[Grace-core] Fwd: [important] Traits

James Noble kjx at ecs.vuw.ac.nz
Mon Mar 11 19:52:26 PDT 2013


So one reason today's discussion was confusing for Michael (& Tim)
(and briefly for Marco I think!)  was that I hadn't forwarded them this email.

I really though I had, which is why I had Michael trying to find it for several minutes. Sorry Guys.

Michael also said "that factory method proposal is the same as the one you had last year called satanic, except you've removed the name satan. It's still horrible".

So: here's the trait proposal, as yet unclassified on the horrible-ness scale.

there are some later thought [in sqbrackets]

J



Begin forwarded message:

From: James Noble <kjx at ecs.vuw.ac.nz>
Subject: [important] Traits
Date: 10 March 2013 6:25:04 PM NZDT
To: Kim Bruce <kim at cs.pomona.edu>, "Andrew P. Black" <black at cs.pdx.edu>


I really liked the Factory Methods proposal. I though that was "it".
But Kim said: "well we can always go back to something simple with
just classes". And then Andrew said --- look, why don't we just do
simple traits? We don't need to do all that stuff, we don't need to
inherit from existing objects or clones, we don't need the complexity.

All you can inherit from are traits.

I said: I can describe that in half a page.

Kim & Andrew said: "great!" :-) 

This is a bit longer than half a page, but rather shorter than factory methods.

So: all objects stand alone; objects are created by evaluating object
constructor expressions; object constructors have a list of
declarations, and can inherit from a single trait expression.  Traits
describe potential structures of objects --- sets of
methods/defs/vars/types/traits - what I'll call slots in homage to
Dave Ungar, until someone thinks of something better.
(James: how about "declarations". James: OK yep, better).

We keep the old syntax, pretty much.
For object constructors:

object { 
inherits trait-expression
declarations...
}

e.g. 

object { 
       inherits Cat("mimi")
       def temperament = "grouchy"
}

but allow an abbreviated form where there are no declarations:

object trait-expression

e.g.   

object Cat("mimi").  

This syntax is pretty close to Java anonymous inner classes
(s/object/new/): the semantics are almost exactly those of Java inner
classes :-) at least as far as we've thought.

For traits we use the class syntax:

trait method-header {
 inherits trait-expression
 declarations
}

trait Animal(name') {
 def name = name'
 var ...
 method o {}
 method another {}
}

trait Cat(name) {
inherits Animal(name)
method..
def...
var...
}

A trait holds a template, a bunch of declarations for creating
objects. Those declarations can only ever be realised into an object
when the trait is inherited into an object construction expression.
The syntax is almost the existing class syntax, except that we either
pronounce "class" as "trait", or spell "class" as "trait" or something
:-) This is pretty much a Java class with nothing static, and only one
constructor, in the header as we do now.

The semantics are exactly those of classical inheritance - with the
restriction that you can only inherit from traits (classes).  Funnily
enough, this is the same as the "Factory Method" syntax, amended so
you can only inherit from classes...  Culling from the factory methods
email:

1. the constructor allocates a new object identity for the object
     under construction.
2. the constructor adds its own methods, and uninitialised slots
     for vars and defs to that object's identity.
3. the constructor invokes the trait(s) it inherits from:
     each inherited trait adds its structure in to the nascent
     object. 
4. inline code and initialisers are run top down, starting with the 
     top-most trait and ending running the code in the initialiser.
     (option: allow inherit clauses anywhere in a constructor/trait
     body, and run them as they are reached). 

And that's pretty much it.  Beginning.  End.  Bye bye inheriting from
clones or objects or true or all that "good" stuff.  
Here endeth the lesson.

What remain are FAQs :-)

Q: Why call them "traits". Why not "classes?  
A: Traits are, basically, classes: indeed the biggest remaining
question is whether to call them traits or classes. We're using the
word "traits" for now, but when I think back on the average SIGCSE
attendee, think about UML, I think we should use the term "class".
(Andrew has some equity in the term "trait".)  At least we will keep
the keyword "class" for the foreseeable future in the compiler, I
presume - but we should reserve the word "trait".

Q: How do you extend dialects or modules or true?. 
A: You can't --- well you have to package any reusable stuff 
all up into a trait and then import & inherit

////////////////////////////////////////
// file StandardPreludeTrait

trait StandardPreludeTrait {
...
} 

////////////////////////////////////////
// file StandardPrelude

import "StandardPreludeTrait" as spt
inherit spt.StandardPreludeTrait 
// nothing else

////////////////////////////////////////
// file My new prelude
import "StandardPreludeTrait" as spt
inherit spt.StandardPreludeTrait 

(I note in passing that the utility of modules & dialects as *objects*
is somewhat diminished by this design)

Q: Are traits objects?
A: yes they are - but they are *not* objects that have the actual
slots implied by their declarations.  (see what I did there - syntax
has declarations but objects have slots. The one produces the other).
They're precisely as much objects as Java classes are Java objects ---
or as much as Grace types are objects.

It's possibly we could make a completely separate syntactic category
for traits: we started there but kept moving slowly towards
reification, so for this email I jumped in with both feet. But we can
climb out again if we want.  [But I think we'll want to "print" anything
in the language - including types & traits. Easiest if traits are 
(meta) objects]

Q: what requests do those trait objects understand?  What's a trait
expression?  
A: Well So far, a trait invocation that looks like a method call.  But
there are a bunch of operators for traits that let them be combined:
the trait objects could understand those operators in just the same
way the type objects (appear to) understand the type operators.  This
is where (apparently) trait algebra can come in to give us MI without
the pain: traits can have operators to combine traits, eliminate
methods etc.  I'm looking forwards to Andrew giving us some
definitions that are legal ASCII and legal Grace.
[objects self-desccribe, so do traits. or they inherit from traits trait.
We do not have smalltalk style meta-regress]

Q: what about abstract, and value, and all that stuff from yesterday?
A: We keep most if it as per the the email.  We keep abstract methods
but not traits or objects: - all traits are of course abstract by
definition, and all object constructors concrete.
[J on reflection, this is Java: it does make sense to have abstract traits…
they can be instantiated without extra bits in the object constructor]

Q: how do you pass a trait around as a parameter? how do you have
multiple constructors? 
A: the way we've always done it: as objects.  Just make an object that
contains a method that instantiates a trait. The code is the same as
it ever was:

def catFactory = object {
 method cat(name) { 
   return object Cat(name) }
}
// ta-RAAH! 

that has a type, and everything. You can add other constructors that
can either call the trait directly or call other constructors:a

def catFactory2 = object {
 method cat(name) { 
   return object Cat(name) }
 method mimi { cat("mimi") }
 method fergus { Cat("Fergus") }
}

so far the same as always, pretty much.  BUT you cannot inherit from
this factory.  If you want to inherit from it **you must make it a
class** (oops, sorry, trait)

trait catFactory3Trait {
 method cat(name) { 
   return object Cat(name) }
 method mimi { cat("mimi") }
 method fergus { Cat("Fergus") }
}

def catFactory3 = object CatFactory3Trait

def catFactory4 = object {
 inherits CatFactory3Trait
 method fergus { cat("confused") }
}

[but if traits are first class (meta) objects, then there is now another choice,
you can actually pass around a trait, rather than a factory producing a trait]

Q: How can we implement this?
A: the same way we would implement factory methods - but its easier
because we don't have extra parameters and ** to talk about --- or
rather we do, but only traits have those extra parameters when they
are invoked from an inherit clause: we don't have to compile two
versions of every factory method, or hack the "callmethod" function.

Q: you mean traits are really just factory methods?
A: yes, but 
 a) you can only invoke them by inheriting them via an object
 constructor --- you can't call them in normal code, and 
 b) you can only inherit from traits!

Q: what about optimisation? making things static so we can understand
programs. 
A: just as with types, traits must be static so we can understand
them, even though Andrew would like total dynamism. 
[this will require annotations because our types are structural.
Nominal languages dump all this other crud into their types,
even when they pretend not to - Scala's stability rules talk about
Defs - but even in Scala there are not supposed to be refs
just accessor methods]

Q: what about aliasing, imports, etc
A: like types, traits are be declared in objects (or other traits) so
they can be in modules & dialcets.
Just as you can abbreviate say

type List = collectionsModule.List 

you could abbreviate

trait CatTrait = farmModule.CatTrait

or even

trait CatTrait(name) = farmModule.PetTrait("cat",4,name)

Q: hey! those look like methods.
A: yes, don't they.

Q: why don't we make them methods then?  then we don't need extra
checking and syntax etc.
A: yes they could be methods return and taking type trait.
but: * we will need exactly the same static constraints from factory methods
    * remember traits can't themselves be objects and you can't
    inherit from objects. If we really want either of those ---
    factory methods loom! 

[OK so maybe we do want first class traits.  or not! help?]


Q: what types are traits?
A: they have Kim's classtypes.   (argh!!!)

Look we claimed a factory method had a type of its return value when
it was called as a normal method --- but that doesn't have nearly
enough information when it was inherited into an object
constructor. We will need all the static information in the trait to
build the new object. To get information hiding across module
boundaries we can have classtypes or traittypes --- but I think we can
encode them in existing types as phantom types:

ClassType< provided_methods, required_methods, overridden_methods>

where those method-lists are each base Grace types.  To do factory
methods properly across module boundaries **we'd have needed to do
this anyway** *(Another reason for separate trait syntax is to infer /
make implicit those types as much as possible.)


Q: what about subtyping of traits etc?
A: Kim!!!




Unfortunately, this means the brilliant paper I was hoping to write
about yesterday's design doesn't need to get written. This design is
basically OCAML with a few tweaks; or Java / C# / Dart classes without
all the hacked-on "static" stuff.  I'm sure it's e.g. a very small
subset of the system already proved in Marco's thesis.  

It's simple. It's Graceful.  It's a wrap.

		



More information about the Grace-core mailing list