[Grace-core] How the class got its dots

James Noble kjx at ecs.vuw.ac.nz
Mon Jun 8 03:50:49 PDT 2015


Hi Kim 

> Here is an attempt to summarize the arguments for the two class encodings.  Please add or modify as necessary/helpful.

thanks for this…

> How the class got its dots:

(we nicked it from newspeak :-)

> We have two options for encoding classes in Grace --  as objects or as methods.  Suppose we start with the following grace class:
> 
> class c.new(n’: Number) -> CType {
> 
>     var n: Number := n’
> 
>     method m(s: String) -> Done {
> 
>        print “called m with {s}”
> 
>     }
> 
> }
> 
> where 
> 
> type CType = {
> 
>     m(s:String) -> Done
> 
> }
> 
> With the object encoding, this is an abbreviation for
> 
> type CClassType = {
>    new(n: Number) -> CType
> }
> 
> def c: CClassType = object {
> 
>    method new(n’: Number) -> CType {
> 
>       object {
> 
>           var n: Number := n’
> 
>           method m(s: String) -> Done {
> 
>           print “called m with {s}”
> 
>       }
> 
>    }
> 
> }

yes.


> With the method encoding this represents
> 
> method cNew(n’: Number) -> CType {
> 
>       object {
> 
>           var n: Number := n’
> 
>           method m(s: String) -> Done {
> 
>           print “called m with {s}”
> 
>       }
> 
> }

yes

> This second encoding is obviously simpler, as we get to leave out the outer “object” in the first encoding.  (Note that with the second, we could omit the “.” between “c” and “new” in the class definition.)

right. in fact we would. 

>  So, why prefer the first? 
> 
> 1.     Creating an object with the dot notation is like sending a message with an explicit receiver.  Early on, most method requests are to an explicit receiver so writing c.new(7) seems more consistent than cNew(7).

yes - although you could always put the class into a module a al Java 
(which goes back to coding standards, again)

> 2.     Renaming on imports is simpler.  Let’s suppose that c is a class in module “outer”.  Then we can write:
> 
> import “outer” as other
> 
> type CType = other.CType
> 
> type CClassType = {
>    new(n: Number) -> CType
> }
> 
> 
> def c: CClassType = other.c

right - and to invoke it you write  c.new( 4 ) 

> 
> To do the same with the second encoding we would have to write:
> 
> import “outer” as other
> 
> type CType = other.CType
> 
> method cNew(n’: Number) -> CType {
> 
>     other.cNew(n’)
> }

yep - or without renaming, you write other.cNew( 4 ) 

I meant to look through your book code to see how much of a problem this was in practice,
but I haven’t yet. 

> The trade-offs are that the definition of c in the first is more compact, though we would have to write out the type CClassType (which is more complex than we would like), while the second requires us to redefine cNew as a new method that calls the original version.  While this could presumably be optimized away, it probably wouldn’t be for the near future, so there is likely a slight efficiency penalty.  (It’s worth noting that if the type annotations are omitted then the first is significantly simpler.)
> 
> 3.     Suppose we want a class to have multiple constructors.  With the first encoding we might write:
> 
> class c.fst(n: Number) -> CType {…}
> class c.snd(k: Number) -> CType {
>    inherits c.fst(… k …)
> }
> 
> At the moment this is apparently not legal,

not it’s not…

> but it could be and could result in an encoding where the object encoding the class has two methods fst and snd that serve as constructors.

that makes the “encoding” rather more complex - 
especially if the “secondary constructors” don’t start by inheriting the primary constructors.

Note that you can *always* have a separate class that inherits from primary constructors in either encoding;
(it gets more fun if you want multiple inheritable secondary constructors though)

> Instead of this notation, we could also write:
> 
> class c.fst(n: Number) -> CType {
>    constructor snd(k: Number) -> CType {
>           fst(… k …)
>    }
> }
> 
> This might make it easier to do the encoding as it is clear that it can be added to the object and you don’t have to worry about the scope in which you can add constructors (Scala does something similar).  I would suggest that we restrict this so that secondary constructors must call the primary constructor.  Though this constructor restriction is as much a matter of good software design as safety.

right. but it also adds a new constructor to the language that we have to define, and further complicates the “encoding” 

> With the second encoding we would write this as
> def c = object {
>    fst(n: Number) -> CType {…}
>    snd(k: Number) -> CType {
>       self.fst(…k…)
>    }
> }

that’s not syntatically correct, fst and snd aren’t anything. there are two options here:

def c1 = object {
   class fst(n: Number) -> CType {…}
   method snd(k: Number) -> CType {
      self.fst(…k…)
   }
}

def c2 = object {
   class fst(n: Number) -> CType {…}
   class snd(k: Number) -> CType {
      inherits self.fst(…k…)
   }
}

depending on the detailed rules we pick, c1.snd may not be inheritable,
and c2.snd may not be valid (it needs class families to work properly)

in either, the self can be implicit in self.fst

> The advantage of this encoding is that if d inherits from c, overriding fst, then snd will automatically adapt to use snd correctly. Andrew uses this in his structures library. However, this will be very rare, as it will require constructor fst to take exactly the same arguments as in c.  This does happen in Andrew’s structures library, but I had a hard time trying to find other examples, as typically a subclass will need more parameters to initialize extra defs or vars.
> 
> The same kind of thing could theoretically be done with the first encoding, where the “constructor” snd could inherit the code calling fst, but again, it will be rare that this works. 
> 
> So what is the best solution?  I personally still prefer the original object encoding over the method encoding because it looks more like a method send with an explicit receiver, however I don’t really see overwhelming evidence for either direction.  Does anyone have any other arguments for one side or the other?

no evidence.  after thinking about this for far too long, and making far too little progress,
I would like to look at your book code and Andrew’s library code again.  But it seems that
if we don’t have “factory methods” or “dotted classes” or “constructors” we end up re-inventing them.

James


More information about the Grace-core mailing list