eclipse-archived/ceylon

consider the kind of capturing when deciding variance

Closed this issue · 6 comments

Consider:

class Foo<out T>(variable T t)
{
    noop(Foo("hello").t);
}

I’d argue that the above should compile. Whether you are only setting, only getting, or both setting and getting the captured attribute should decide whether it is, respectively, contravariant, covariant, or invariant.

You might be wondering why one would mark the attribute as variable if one is not going to set it. But the catch is: one may be setting this.t, and not other.t.

In my particular use‐case, I have an attribute that is identifiable, immutable, and potentially expensive to check for equality. I want to set this attribute to the comparee so that I can use identity equality instead of value equality next time around.

Here is a simplification of what I’m trying to do:

class Foo<out Expensive>(variable Expensive expensive)
    given Expensive
        satisfies Identifiable
{
    hash => 0; // TODO
    shared actual Boolean equals(Object that)
    {
        if(is Foo<Expensive> that)
        {
            if(expensive === that.expensive)
            {
                return(true);
            }
            else if(expensive == that.expensive)
            {
                expensive = that.expensive;
                return(true);
            }
            else
            {
                return(false);
            }
        }
        else
        {
            return(false);
        }
    }
}

Of course, this limitation can be worked around by having a second non‐variable attribute that delegates to the variable attribute and capturing that instead, but I think the compiler should be smart enough for the developer to not have to think and worry about that.

The other option would be to get rid of the capture analysis and instead acknowledge an implied in Nothing (or out Anything for in TPs):

class Foo<out T>(variable T t, Foo<T> other) {
    t = other.t;       // ok
    other.t = nothing; // ok
    other.t = t;       // error, T is not assignable to Nothing    
}

So the problem is that this unsound in the general case. Although your first example is sound, the reason that it's sound is not expressible in Ceylon's type system.

@RossTate I'm not sure I follow, since out T can imply in Nothing. This is already the case for use site variance:

class Foo<T>(shared variable T t) {}

void run(Foo<out String> foo) {
    String s = foo.t; // ok
    foo.t = nothing;  // ok
    foo.t = foo.t;    // error
}

// error: assigned expression must be assignable to declared type:
//        'String' is not assignable to 'Nothing'

After eyeballing the code, this looks pretty easy to implement.

I have implemented this suggestion on the branch 7311. Please try it out.

OK, well, this seems to be working, and I'm very happy with the code improvements I made as a side-effect, so I have merged the branch. Closing.