Skip to content

Instantly share code, notes, and snippets.

@MinusKelvin
Last active May 26, 2017 14:30
Show Gist options
  • Save MinusKelvin/e3238eae62c677a7504265f7f645c2dc to your computer and use it in GitHub Desktop.
Save MinusKelvin/e3238eae62c677a7504265f7f645c2dc to your computer and use it in GitHub Desktop.
Language feature troubles

Value types implementing interfaces

Interfaces are references that live on the heap. These are obviously compatible with reference types, since the interface object is laid out as a pointer to the object and a pointer to the virtual function table. For value types, we have to allocate space for the type on the heap and point to that in the interface object.

Since value types are semantically immutable, they cannot modify their state in any of these interface methods, making them semantically different from class implementations, which can. This practically invisible difference in semantics is a flaw in the direct approach to this problem

Distinguish between mutable and immutable interfaces

This would be done, probably through another keyword (like trait) to specify that the implementor of the interface is not allowed to mutate the underlying object. This causes confusion in reference type methods. What is the type of this? this is already constant, since it is illegal to assign to this. That must mean that this is an immutable type.

To indicate the fact that this is immutable, it must be explicitly stated by the implementing method. However, following the rule of everything is constant unless explicitly stated otherwise, this means that all methods that want a mutable reference to this must be marked as such. This would make the type of this inexpressible elsewhere in the language, either preventing us from doing a number of reasonable things, such as put it in an array, or breaking the immutability of the reference:

public class SomeClass {
    private vary x = 0;

    public fn breakImmutability() {
        assert(this.x == 0);
//      this.x = 5;                             // Error: cannot assign to field of immutable object
        let array = new Array<SomeClass>(this);
        array[0].x = 5;
        assert(this.x == 5);                    // Broke immutability
    }
}

Fixing this would require that mutability be included in the type information of the object. Methods that require a mutable reference to this will only be available on the mutable variant of the type. Immutable variants are available on both, since mutable types are implicitly cast to immutable types. However, different logic may be preferred (or possibly required) depending on if the object's type is mutable or immutable, and this information is not available due to the fact that immutable methods can be called on mutable types. For example, consider String.substring:

public class String {
    private Array<Char> chars;
    private vary start = 0;
    private vary length = 0;

    public String() {
        chars = new Array<>(16);
    }

    public String(Array<Char> chars) {
        this.chars = chars;
        length = chars.length;
    }

    private String(Array<Char> chars, int start, int length) {
        this.chars = chars;
        this.start = start;
        this.length = length;
    }

    // For when our type is immutable String
    public fn substring(int start, int end) {
        return new String(chars, start, end-start);
    }

    // For when our type is mutable String
    public fn substring(int start, int end) {
        return new String(chars.copySlice(start, end));
    }
}

In this case, we have two different implementation of String.substring, but it depends on if the instance of String is mutable or not, not on whether our view of it is mutable or not. This means we should not allow casting between mutable and immutable types, however this will result in lots of duplicate code. I can think of no good solution to this problem.

Lack of implicit nulls

Consider the following code:

let ary = new File[3];

What values are the elements in the array ary initialized with?

Create from sized stream

let filenames = ["foo.txt", "bar.txt", "baz.txt"];
let ary = Array.create(filenames.stream().map(s -> new File(s)));

Initializer function

let ary = Array<Option<File>>.create(() -> None);

Is actually an array list

let ary = new ary[3];
assert(ary.size == 0);
assert(ary.capacity == 3);
ary.add(new File("foo.txt"));
assert(ary.size == 1);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment