gracelang / minigrace

Self-hosting compiler for the Grace programming language
39 stars 22 forks source link

Fresh methods don't have manifest shape #251

Open apblack opened 7 years ago

apblack commented 7 years ago

A method that tail-returns an if(_)then(_)else(_) is treated as fresh provided both branches of the if are fresh. There also needs to be a check that both branches return objects of the same shape.

kjx commented 7 years ago

but is only manifest if the "if" test is manifest?

we're way way down the rabbit hole now, and doesn't this complete back up Eelco & Vlad's point..?

apblack commented 7 years ago

We have never said that the particular object returned from a fresh method should be manifest. in fact, the identity of the object can never be manifest, because of the fresh object restriction. What we say in the spec is that the shape must be manifest.

So I think that it's fine to inherit from a method that returns, for example, either a rangeUp or a rangeDown object, provided that they have the same shape.

kjx commented 7 years ago

I didn't mean the object: I meant that given if (e) then {object { ...}} else {object {...}} should the e be manifest. Can there be different initialisations & inline code inside those objects? Does the shape of an object include method bodies?

KimBruce commented 7 years ago

First, we clearly need a crisp definition of shape of an object. This is related to the "annotation type" of an object that I've been trying to write up. (See issue 129 in language.) The shape of an object should be the set of features of the object (public and confidential) with the associated type annotations at creation. We need both public and confidential when inheriting in order to determine conflicts when defining subclasses. It does not include method bodies or initialization code.

Theoretically this could be OK in Andrew's example of a conditional with each arm returning an object with the same shape. However, I would argue against this as it adds complexity for little or no benefit for novices. Surely if we allow anything fresh where all execution paths return something of the same shape we will end up with something that is undecidable! Let's find a nice simple conservative algorithm.

KimBruce commented 7 years ago

Here is another program that runs fine but should be illegal:

def o1 = object {
    class c {
        method m {print "in c"}
    }
}

def o2 = object {   
    class d {
        method n {print "in d"}
    }
}

method build(o') {
    def b = object {
        inherit o'.c
        self.m
        print "in build"
    }
    b.m
}

build(o1)

We should not be able to inherit from something coming in as a parameter because we don't know the shape of the new object. Of course it will blow up if we write build(o2), but with method not understood rather than a problem with inheritance. (You do get an indication of the problem if you drop the "self." before the m as it doesn't know whether there is an m and you get an error message -- I'm not sure why there is no compile-time message if you add the self.)

apblack commented 7 years ago

I'm not sure why there is no compile-time message if you add the self.

It's because in that case there is no need to do name resolution. So the no one checks to see if self has an m: we just go ahead and make the request, and see if it works.

apblack commented 7 years ago

With regard to this adding complexity with little benefit for novices: you are quite right. This feature was added to enable code optimization for lists, which mattered for the library writer. The correct fix for this might be to stop classifying methods that tail-return an if(_)then(_)else(_) as fresh. The point of this issue was initially to record the problem; how we fix it is completely open.

kjx commented 7 years ago

We should not be able to inherit from something coming in as a parameter because we don't know the shape of the new object

unless we had manifest annotations - then we could inherit from a manifest parameter.