[Grace-core] Classes and inheritance

Michael Homer mwh at ecs.vuw.ac.nz
Tue Jul 12 21:13:10 PDT 2011


Hi,
I'm working on getting these going at the moment, and I've come across
a few areas that need clarification:

(1) What can appear on the right-hand side of "extends"? I currently
restrict it syntactically to <identifier> "." "new" [ "("
<identifier>+ ")" ], which is then resolved as an expression within
the surrounding scope. Should there be any further restrictions on the
identifier there?

(1a) When should the superclass be resolved? It isn't necessarily
known at compile time, since the binding of <identifier> may change
dynamically, and it could even change in between instantiations of the
class. There's no guarantee that it's a constant, and if someone
writes their own "class" using an object literal, like the spec
encourages, the "new" method may even return something different each
time.

(1b) Related to both of those, there is a typing issue here of some
sort: the instances of the class should also be instances of the type
of the instances of the superclass, which I think means something
complicated for the resolution rules.

(2) How can you write mutually-recursive classes? Since "class X { }"
is syntactic sugar for "const X := object { ... }", something like
this has scope problems:
  class A {
    method asB { B.new }
  }
  class B {
    method asA { A.new }
  }
Error: reference to undefined identifier "B" at line 2.

(3) What is the behaviour of "super"? It can be used as a receiver, so
any method can be requested on it, but where does that search start?
Given the concatenative semantics it seems that the only possible
place is immediately before the current method, which may be
unintuitive. Concretely, what does this output:
  class A {
    method speak { print "Hello" }
  }
  class B extends A.new {
    <override> method speak { print "Goodbye" }
    method test { super.speak }
  }
  B.new.test
? B.new ought to be equivalent to:
  object {
    method speak { print "Hello" }
    <override> method speak { print "Goodbye" }
    method test { super.speak }
  }
, in which it's clear the only possible output is "Goodbye", but that
may not be what people expect from super. The latter could be an error
"no superclass", but then super has to be eliminated entirely in the
rewriting.

(3a) Is that object rewriting correct? In fact I currently rewrite
"class B extends A.new" to "object { method new { object extends A.new
{ ... } } }" because it was a simpler step in my implementation, but
in the current specification only classes can extend things and there
are no anonymous subclass literals. In the object extends rewriting
there is a clear superclass, and super would be able to skip over all
methods in the current scope, but it becomes possible to write methods
that can never be invoked by any means.

James's phrasing on this was whether methods had a total or a partial
order, which I think is an overlapping issue but not exactly the same.

(4) How do "self"-less method calls overlap with dynamic typing and
inheritance? If I can write "value" and have it refer to self.value,
if defined, but otherwise be resolved lexically to either a local
variable or an outer method, what happens when the superclass of self
is dynamically typed and it's unknown statically which methods are in
it? Do all bare identifiers resolve lexically, or is there a runtime
lookup instead?

(4a) Are methods defined in the surrounding scope (i.e., on "outer",
if that's what it's going to be called) in scope above their
definition? "outer.foo" I think ought to work regardless, but does
"foo" resolve to that as well?

Those two have implications for the no-shadowing rule as well.

(5) Not a question, but a note on implementation. With a
dynamically-typed superclass, or a statically-typed (but not
statically-bound) one, there's no way to know statically how much
space to allocate for field storage, or to determine a fixed offset
for each datum at compile time, and so you have to fall back to
something approaching a hash-table lookup. It isn't an utterly
unresolvable problem, and you will need to do that anyway for method
lookup in a structurally-typed world, but it's may be a point to
consider in the design even though efficiency isn't a goal.
-Michael


More information about the Grace-core mailing list