[Grace-core] How the class got its dots

Kim Bruce kbbruce47 at gmail.com
Sun Jun 7 21:51:08 PDT 2015


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

How the class got its dots:

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}”

      }

   }

}

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}”

      }

}

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.)

 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).

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

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’)
}

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, 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.

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.

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

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?
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mailhost.cecs.pdx.edu/pipermail/grace-core/attachments/20150608/1d364e52/attachment.html>


More information about the Grace-core mailing list