The Member of Generalization Problem Design questions
Semantics | Home

  Background

Like Java and C++, Eidola allows classes to be members of other classes. Unlike Java and C++, however, a public member class is part of its owner class's signature. A class inherits or overrides its parents' public member classes, just as it inherits or overrides all of its parents' public members. This turns out to have intriguing consequences.

Three semantic rules are important to the discussion which follows. The first, the Big Important Rule of Subtypes, says that every public member of a parent must have a matching public member in the child.

(1)

The second, the inheritance rule, allows a class to pull public members from its generalizations.

(2)

The third, the matching rule for classes, makes it possible for a class to override member classes from its parents.

(3)

See the semantics reference for more details.

  The Question

Consider what happens when a class is a public member of its own generalization, as in the following example:
   Class x
     public:
       Class x.y extends x
         public:
         private:
     private:
There's a problem with this: by (1), x.y needs a public member matching every public member of x. Since x.y is itself a public member of x, it needs to contain a public member which matches itself. We could use (3) to create a new member of x.y to satisfy (1):
   Class x
     public:
       Class x.y extends x
         public:
           Class x.y.z extends x.y
             public:
             private:
         private:
     private:
The problem with this is that x.y.z now has the same problem x.y had before. This whole venture may seem inherently silly at this point, but (2) gives us an unexpected alternative: x.y can inherit itself.
   Class x
     public:
       Class x.y extends x
         public:
           x.y (inherited)
         private:
     private:
Yes, I know, this is really, really strange. But look hard -- the semantics as they currently exist do allow this: x.y is allowed to inherit itself under (2), because
  • x.y's owner is x,
  • x is a generalization of x.y, and
  • x.y is a public member of x.

So the question is, should we allow this?

  Arguments Against

It smells funny. Most everyone's first reaction to this is "that can't be right." I discovered it fooling around with the kernel, and at first thought this behavior was just a bug. Even though it is legal, it's hard to grasp -- like Goedel's theorem, it initially makes you feel like you've somehow been hoodwinked. It is unlikely that something so contrary to intuition is really desirable.

It contradicts some intuitions about subtypes. Dave wonders whether a type can meaningfully contain its subtype, even at the intuitive level. An instance of a subtype is somehow "bigger" than an instance of a type -- the subtype says of its parent type, "I am all of that, and more." So in some sense we're talking about a type containing something larger than itself. Again, the parallels to set theory paradoxes are obvious.

It might makes things harder. A strange structure like this might make runtime semantics and kernel support trickier. I don't see for sure that it will, but it might. This is a less important argument than the others, however; I prefer not to let ease of implementation be the primary guide for any portion of this consciously experimental project.

Such expressive power could be used for evil. A construction like this cries out to be abused by programmers trying to be too clever for their own good (and goodness knows, that's just about all of us at one time or another). It might be possible to express some useful concepts using this mechanism, but there will probably be alternatives, and they will probably be better ones.

  Arguments in Favor

It works; why mess with it? I am leery of disallowing something in the semantics without a really concrete and compelling reason, which none of the above are. This construction reveals no contradictions in the semantics, and the kernel is perfectly happy with it. It may be counterintuitive, but it's the natural result of a set of carefully chosen rules. The rules make sense and are self-consistent; if we are surprised by their results, it may be our intuitions and not the rules that need to change.

Expressive power is good. Languages have to trust programmers to exercise their good judgment. A language should set up a self-consistent structure, and let programmers use its full expressive power as they see fit. You can't stop a messy programmer from making a mess, but disallowing perfectly legal constructions could stop a thoughtful programmer from setting up the structure they think best.

It may actually be useful. Nick invented a rather startling example, in which the member-of-generalzation construct makes some sense. Suppose we want to represent the following system: a statement is any mathematical assertion, which may be true or false. A theorem is a true statement with a proof. An axiom is a true statement which has no proof. An finally, every statement can have associated corollaries, which are directly related statements. (For the purpose of this example, we assume that every corollary is related to exactly one theorem; if it follows from multiple theorems, it's a new theorem.)

Got all that? Here it is in Eidola, using members of generalizations.

   Class Statement
     public:
       Class Statement.Corollary extends Statement
         public:
           Statement.Corollary (inherited)
         private:
     private:
    
   Class Theorem extends Statement
     public:
       Variable Theorem.proof : Proof
       Class Theorem.Corollary extends Theorem, Statement.Corollary
         public:
           Theorem.proof (inherited)
           Theorem.Corollary (inherited)
         private:
     private:
    
   Class Axiom extends Statement
     public:
       Class Axiom.Corollary extends Theorem, Statement.Corollary
         public:
           Theorem.proof (inherited)
           Theorem.Corollary (inherited)
         private:
     private:
    
   Class Proof
     public:
     private:
Amazingly, the entailments of this beast seem correct: every corollary is associated with a statement; every theorem is true and has a proof; every axiom is true and has no proof; a theorem's corollary is a theorem; an axiom's corollary is a theorem; an axiom's corollary's corollary is a theorem's corollary, not an axiom's corollary...and so on. Think about that one...befuddled yet? You can futz with it yourself and draw your own conclusions -- get the Java kernel and try the input script that made this example.

The compelling thing about this example is not that it's the only way to implement this structure (it's obviously not), or even that it's the best way. Rather, it's that when we implement a familiar system using this unfamiliar system, everything remains self-consistent, and the implementation is a solid metaphor for the concept it implements. That is, of course, exactly what a high-level language should do!

  The Verdict for Now

...is to allow it, and see how things play out. If the algorithmic and runtime semantics don't turn up any contradictions, it may be illuminating to just write a few programs and see this construct in action. At that point, it should be clearer whether this is a good idea.

  Design questions | Semantics | Home Copyright 2000-2001 Paul Cantrell