Closed apblack closed 8 years ago
So: I thought about this question last night. I really want to make progress and fast, and I’m on holiday until Monday with spotty internet access and little time to think. So - to make progress - I propose we adopt this. Then at least we have something we can specify, and know what to describe, and Andrew can implement it!
The sum operation is defined in Section 4 of S. Ducasse, O. Nierstrasz, N. Sch ̈arli, R. Wuyts, and A. P. Black. Traits: A mechanism for fine-grained reuse. ACM Trans. Program. Lang. Syst., 28(2):331–388, 2006
I haven't really looked in any depth - but another decisive advantage of this design is that we already have a formal semantics for it in that paper.
I don't want to re-open #38 because I see no other way to make progress. But I got a working Internet for a bit, so I recorded my remaining questions about this proposal at #39
Clarification: traits can capture state. Everything in Grace can capture state, so it would be very hard to exclude this case. What would the error message say?
I came up with this in the shower this morning, after spending an hour or so implementing trait method collection and checking.
graceObject
. It was gifted its method from the ultimate creator of all things.graceObject
, unless they specify some other object in an inherits statement.graceObject
.)graceObject
, as well as those obtained from used traits.graceObject
.I think that this has the right effect, and has the advantage that we don't have to change our existing inheritance story.
I think this is OK - it's a variant on the "default methods are treated specially in traits" except we identify default methods by their being in graceObject rather than elsewhere.
and has the advantage that we don't have to change our existing inheritance story.
not sure quite what you mean by "don't have to change". It's a tweak, but not a very big one. What I like about this is that object { } is fundamental, and (almost) everything else can be defined in terms of it (except I guess graceObject?)
This also relates to Nixonianism #59 and default methods (obviously) #49
notably, there are a range of possibilities (this text also in #49)
One consequence of this is that any systems code, particularly in debugging etc, can never really depend on default methods: this kind of defensive coding is always required: https://github.com/gracelang/language/issues/39#issuecomment-183551871
this seems to be waiting on #47 (manifest) #49 (default methods) in particular and a bunch of other ones...
(where I've got to)
EITHER traits cannot have default methods
OR we have to complicate the already crazy inheritance/trait “algorithim”, picking one of:
Note that handling default methods based on their names doesn't work, neither does class (or trait priority) -- because e.g. you might have either traits or classes either using or requiring default methods.
I think the inheritance rules #67 are already very complex. I don't want to add in more special cases. So I think traits should not have any default methods. This is less of a problem than you might think, because you cannot trust default methods anyway.
OPTION 1: no object constructor gets default methods; class shortcut somehow adds them in
OPTION 2:
In other words, it seems programming with objects has to have a small bias towards either making traits or making classes...
Traits should not have default methods, objects (and hence classes) should. Traits just have methods (or perhaps a very little more) Objects are traits with defs and vars, initialization code, and default methods.
What's wrong with the rules that I set out above? I've implemented them, and they seem to work.
Doubtless I've missed something. Can you tell me what it is, before we start on this all over again?
What's wrong with the rules that I set out above?
Those rules "handles the traits definition the default methods specially" (4,5), and also as "graceObject" which is special (1). Plus "uses" seems to work differently from "inherits" at least some of the time. Re-reading, I'm not sure how _"When... climbing the inheritance chain, all methods are included as well as those obtained from used traits" is modified by "trait usage chain, all methods from all used traits are included. except those from graceObject
."_ Perhaps none of these points are important.
I've implemented them, and they seem to work.
I'm in no way proposing you should change anything you've implemented in the short term (i.e. before your course runs)
Kim - so do you mean you'd be happy with OPTION 2 above?
if not, we're back to Andrew's original four-part proposal: either traits cannot be objects (so must be something else); or raw traits cannot be "instantiated"; or the rules get more complex and assymetric
Yes, those rules handle inherits and uses differently. I view that as a feature of uses — that it behave differently from inherits. (If it behaved the same, why on earth would we have two keywords?)
And yes, graceObject
's methods are treated specially. I'm not sure that's necessary, but it seems to be what programmers will want most of the time.
Having default methods is a compromise. I agree that the language would be purer, and simpler, if we didn't have them. But they make programming easier for novices.
It's the same story with having different default visibilities for methods, defs, and types (what is the visibility of a type?). They complicate the language, but make programs simpler.
In the end, it's a matter of taste how far one goes with this. I think that you have gone overboard by saying that alias
also changes visibility (but that may be because I don't yet know how to implement the change in visibility). But that does not mean that there is a "theorem" that traits can't have default methods.
it's a matter of taste how far one goes with this
absolutely. the problem is we have different tastes :-)
(If it behaved the same, why on earth would be have two keywords?)
because we want to distinguish between traits and classes, and these two keywords help us make that distinction (i.e. the big distinction is you get only one inherit
clauses and only that inherit
clause can inherit from a class). That distinction still holds even if the underlying semantics are the same.
Having default methods is a compromise. I agree that the language would be purer, and simpler, if we didn't have them. But they make programming easier for novices.
On default methods at least I agree that we need them: I'm trying to find the smallest tweak to the language that gets them.
funnily enough, talking about this stuff for the other project with Mark & Sophia
saying that alias also changes visibility
I know Kim likes this, but I'm... agnostic.
But that does not mean that there is a "theorem" that traits can't have default methods.
I'm not saying that there is a "theorem" that traits can't have default methods; I'm saying the coherent design options seem to be that either traits cannot have default methods, or that we have to complicate (an admittedly already complex) part of the language.
Flattening #71 has been important to me since the Pomona meeting, and if anything it has got more important to me since then. What I'm after (in chasing flattening) is as simple as possible a description of what inheritance & trait composition actually does: I think they are sufficiently tangled so they have to be treated together. To me, aliasAndAbstract makes vars & defs simpler to explain than our current collection of alias and exclude; making uses
have the same as semantics as inherits
; not having special cases in precedence etc (although of course I still want MY special cases in the parser)
In the end, it's a matter of taste
yes, and it's only after re-reading the TOPLAS paper this week that e.g. I realised how close our current design is to the Smalltalk traits design - and not e.g. a simplification of it.
yes, and it's only after re-reading the TOPLAS paper this week that e.g. I realised how close our current design is to the Smalltalk traits design - and not e.g. a simplification of it.
I think that's because you can't simplify it any more and still make it useful. Almost all other trait implementation make it much more complicated!
One simplification that we have made is to eliminate super from inheritance, and use the same alias & exclude mechanism for both inheritance and trait usage.
Let's recall how we got to the above treatment of default methods. A class isn't something that uses the class syntax — it's a method that tail-returns a fresh object. Similarly, a trait isn't something that uses the trait syntax, it's a class that happens to obey the "tritely" restrictions. So we can't make the existence of default methods depend on the use of the trait or class syntax — that won't do what we need.
The alternative "creation myth" that came to me in a vision two weeks ago now, and (I think!) have implemented in minigrace, is that whether you get the default methods depends on whether you use uses or inherits. I think that this works, but we won't really know until we have programmed with it a bit. That's what I want to do now — stop talking about this and get down to writing code using it.
I wrote some stuff in the spec about default methods. https://github.com/gracelang/language/blob/portland-james/spec.md#default-methods
The spec now says:
the objects created by the class syntax inherit from graceObject if no inherits clause is supplied (but not objects created by the trait syntax or by object constructors
and
Some objects, notably instances of raw traits and done do not conform to Object
rather than
Notice that the public methods implicitly inherited from
Object
are implicitly included in all types.
https://github.com/gracelang/language/blob/portland-james/spec.md#type-object
The above links to the spec no longer work. Here is one that does: http://web.cecs.pdx.edu/~black/OOP/GraceResources/spec.html#type-object
I've updated the spec (version 0.7.3) so that the section on Type Object and the section on default methods are consistent. So I'm closing this issue.
Semantics of Traits and Trait Composition
During the Grace teleconference on 9th February 2016, I was asked to write down the semantics that I expect from traits and trait compositions in Grace.
Object Constructors
Everywhere in this document, when I write Object Constructor, I intend to include also the class shorthand, since this syntax is defined as equivalent to a method whose body is an object constructor.
Object constructors are unchanged by this proposal. Any object constructor without an explicit inherits statement implicitly inherits from
graceObject
.We might want to introduce a way of eliminating the automatic inheritance, e.g., inherits
nothing
, wherenothing
is a pre-defined identifier.Trait Constructors
I propose that we invent a syntax for trait constructors. I further propose that this be the same as the existing syntax for object constructors, except that it is preceded by the keyword trait, and that the body of the trait constructor be restricted to containing only uses statements, method definitions, type definitions, and comments; inherits statements, field declarations and top-level code are not allowed. [An alternative syntax using the annotation
is trait
on an object constructor isn’t legal, becausetrait
is a keyword, and annotations require identifiers.]Everywhere in this document, when I write Trait Constructor, I intend to include also the trait shorthand. Parallel to the class shorthand, the trait shorthand shall be defined as equivalent to a method whose body is a trait constructor.
Trait constructors can’t inherit, so there is no implicit inheritance of
graceObject
by a trait constructor.Any object that satisfies the restrictions on traits (contains only methods and types) is defined to be a trait, and can be used by another trait or object. The purpose of the trait constructor is to provide error messages if a programmer inadvertently breaches the trait restrictions. Note that, as a consequence,
graceObject
is itself a trait, and can be used by an object that wishes to re-acquire one of the default behaviors.Inheritance
I’m not proposing any changes to the way inheritance works as part of introducing traits. (I would like to see a drastic simplification of inheritance, but that is not part of this discussion.) Briefly, the way that I think inheritance works at present is as follows.
Trait Use
a
and a trait containing a methodb
is a trait with attributesa
andb
, whose bodies are the bodies ofa
andb
in the component traits.a
with another trait containing an attribute nameda
is a trait with an attribute nameda
whose body is the join of the bodies of the bodies ofa
in the composition. If the bodies are equal, then the join is that body; otherwise, the join is the distinguished value traitConflict. We can approximate equality conservatively; in particular, if the two bodies are parameterized, we can assume them to be different, even though the value of the parameter might be the same in both cases.Default methods
Before traits, all objects by default inherited a few useful methods, notably
=
,≠
,::
, andasString
. This remains true for object created by object constructors, but it’s no longer true for objects created by trait constructors. This means, for example, that you can’t put a trait in a dictionary (at least, not in the usual way.)I don’t think that this is a big problem, since an object or trait constructor can only use a fresh trait, not one that’s been stored in a dictionary. But the loss of
asString
and the concomitant ability to print any object is a practical problem.We could solve this by instead by putting the default methods into trait objects, but giving the uses clause the special property that if a component trait contains a method with one of the default names and the default body, then that method is excluded from the composition. (I think that this is kjx’s option 2 from issue 35.) I must admit that this is a kludge, but I’m concerned that not having
asString
will become a problem. Perhaps the best thing would be to try getting along without the kludge (so that traits don’t have an asString), and see how hard it bites.