[Grace-core] Class syntax and type parameters

Michael Homer mwh at ecs.vuw.ac.nz
Tue Nov 11 16:20:15 PST 2014


On Wed, Nov 12, 2014 at 12:08 PM, Andrew P. Black <black at cs.pdx.edu> wrote:
> Michael,
>
> I believe that it would really help both you and the project as a whole if
> you took a few moments to explain what you are saying, both in your emails
> and in meetings, and why you come to these conclusions.   You are obviously
> very smart and very quick, but these traits help us if you don’t trouble to
> explain your reasoning.
>
> On 11 Nov 2014, at 13:42, Michael Homer <mwh at ecs.vuw.ac.nz> wrote:
>
> On Wed, Nov 12, 2014 at 10:04 AM, Andrew P. Black <black at cs.pdx.edu> wrote:
>
> More on classes with type parameters.
>
> Here is the use case that prompted the question that I raised in the
> teleconference yesterday.
>
> factory method list<T> {
>    inherits collectionFactory.trait<T>
>
>    method withAll(a:Collection<T>) -> List<T> {
>        object {
>            inherits indexable.trait<T>
>            var inner := _prelude.PrimitiveArray.new(a.size * 2 + 1)
>            var size is public := 0
>            for (a) do {x->
>                inner.at(size)put(x)
>                size := size + 1
>            }
>>
> Notice that it’s necessary for the type parameter to go on the constructor
> object `list` rather than on the method `withAll()`: if the parameter is on
> `withAll()`, then it’s nor accessible on line 2 where it is needed to
> parameterize the inherited trait.
>
> So we have inconsistent naming:
>
>        list<T>.withAll
>
> but
>
>        collectionFactory.trait<T>
>
> because the latter is declared with the class syntax, and therefor can only
> — by virtue of Michael Homers pronouncement — be parameterized as above.
>
>> What you're doing is fundamentally unsupported by the inheritance
>> system,
>
>
> Could you please explain what you mean when you write “What you're doing”.
> I’m doing several things at once — which one are you referring to?    Do you
> mean using inheritance to share (pure) methods between objects?   I agree
> that Grace’s inheritance system needs work, but that is surely something
> that it can do.  Do you mean adding type annotations to the collections
> prelude?   I don’t see that as “fundamentally unsupported” either — there
> may be deficiencies in the type system, but fundamentally we do want it to
> be able to cope with typed collections.   I’m at a loss to understand your
> point — although it’s clear that you feel very passionately about it.
Inheritance only exists when creating a new object. It's intended that
that object be the lowest-level creation that the user receives.
You're creating extra fresh objects as a hack to get inheritance
behaviour to appear in additional places, which exist momentarily and
are then forgotten other than for their scopes.

It's fundamentally unsupported in the same sense that, say, logic
programming is fundamentally unsupported: you can build something to
make it work, but it's not going to be as nice or consistent with the
rest of the language as if it were built in.

So "What you're doing" is the lines from "factory method" through
"inherits indexable" and everything that's going on there. I think
it's outside what the inheritance design tries to facilitate, and
consequently unidiomatically inconsistent with other code.

I get the impression from this that you saw enabling this sort of
approach as a deliberate part of the design, which I certainly didn't
think it was. I wouldn't think of this as a suitable design for that.
If it's necessary to have this sort of extra parameterisation, maybe
introducing some sort of partial or deferred application is useful. If
mixins, family polymorphism, generalisation, trait algebras, multiple
inheritance, etc, are really useful, maybe introducing them properly
is worthwhile, rather than trying to wedge them in artificially and
being disappointed when they're not quite good enough. The inheritance
system as currently designed is an extremely simple system with
inheritance only for behaviour-sharing at the user object level and
doesn't try to enable any of these, even though it's possible to
replicate some of the behaviour.
>
> This motivated by original question: does
>
>        class a.b { object constructor body }
>
> mean (1)
>
>        def a is public = object {
>                method b {
>                        object { object constructor body }
>                }
>        }
>
> or (2)
>
>        method a  {
>                object {
>                        method b {
>                                object { object constructor body }
>                        }
>                }
>        }
>
> I had always assumed (1), but now (2) looks more reasonable.
>
>
>> #2 has all the disadvantages of both dotted and non-dotted classes
>> while managing to avoid picking up any of their advantages. (The same
>> applies to your original list at the top, in fact.)
>
>
> #2 wasn’t supposed to have any of the advantages of dotted classes or
> non-dotted factory methods — it was supposed to be a suggested translation
> for the dotted class notation (what Kim would call an “encoding”, I think).
I'm not saying it's mimicking either of them - I said it had all the
bad points of both with none of the good ones.

The inference you might take from that is that you should prefer
either of the others instead, since they at least still have their own
good points: either have classes be objects or forget having any
first-class representation at all and just make them methods.
Splitting the difference to make something worse than both isn't an
improvement.

Another inference you could take is that they all have problems and a
fourth approach may be required if they're going to be used in these
ways. Classes are a giant hack themselves, only there for
appeasement's sake.
>
>> There is a fundamental choice to be made about whether you want
>> classes to be first-class entities before or after generic
>> instantiation. You can't have both.
> Well, we could have both, if we made functions first-class.   But I agree
> that we haven’t done that yet, and for good reason.
> Most programmers will never see the difference between (1) and (2).   With
> the first translation, `a` denotes an object.  With the second translation,
> `a` is a method request that answers an object.  Since that object itself
> captures no state,  the difference is pretty much philosophical, I think.
> If `a` has type parameters, then those are implicitly given the value
> `Unknown` before the method request is made — `a` is still legal.
The aliasing behaviour becomes a bit odd at the point that they're
methods, though, and there's nothing to clue you in when you're not
writing the <>. It probably isn't a huge concern, but it's an oddity
to encounter.
>> I don't know what people want. If
>> you're programming without types then "after" is quite confusing.
>
>
> Don’t we have exactly the same confusion with parameterized type definitions
> like List<T>?    List is strictly a (type) function, but I can use it as a
> type, where it means List<Unknown>
If you're programming without types you're unlikely to encounter a
type to be confused by.

Conceptually generic types are functions from types to types and in
the presence of reification that may be necessary in practice. That
does seem to make them methods. Tim's "let" declarations might come
into it too - there really does seem to be another conceptual category
here that's necessary to allow types to do what they need to, and
possibly classes too. In that case there's a clearer meaning of
aliasing and so forth.
>> "Neither" is non-dotted classes.
> Are you saying that, if we don’t have the dotted class notation, then there
> is no question to be answered?  That seems obvious; if we just have factory
> methods then there is only one method that be parameterized by type, so it’s
> clear which method is parameterized by type.   I have a feeling that you are
> intending to say something less obvious, but I’m at a loss to know what.
I provided three options: classes could be first-class entities either
"before" (def cls = object) or "after" (method cls<> {object}) generic
instantiation, or in "neither" case (method cls<>(...){}). Those are
all viable translations and they've all been proposed at some point. I
was just setting out the three options in terms of what they implied
with respect to generic instantiation and when the class is a
first-class entity: before generic instantiation, after generic
instantiation, or neither.

I'm hoping Tim has something written up about "let" that he's about to
send off, because I think that's the productive avenue for solving
this sort of thing.
-Michael



More information about the Grace-core mailing list