Modular Smalltalk was conceived with multiple inheritance in mind, and I've logged somewhere on the wiki already the disparity, therefore, between Modular Smalltalk and triple scripts. However, because we retain some dynamism where Modular Smalltalk tries to avoid it, authors can still approximate it. Can we make it easier by providing first-class support? I'm not intimately familiar with Ruby, but I know it has something like what follows.
Consider some trait that allows us to send a "foo" message to a class, i.e., the class implements a method foo
. We want our class Bar
to support this trait, and more specifically, we want to piggyback off foo
implemented elsewhere. In our example, the implementor is an existing class FooTrait
. In precompilation form, we might allow:
import { FooTrait } from "./FooTrait.xxx" export class Bar { constructor() { /* ... */ } } FooTrait.foo extends Bar
In post-compilation form, we get:
/// import { FooTrait } from "./FooTrait.xxx" /// export class Bar { constructor() { /* ... */ } } Bar.prototype.foo = FooTrait.foo;
This is dependent upon the eventual break with TC39 (once again, not that big of a deal, since after all This is not JS).
You might ask why we don't sidestep the entire schism, though, by simply not introducing special syntax in the first place, i.e., just prescribe the static assignment form. Well, we get richer semantics. Note, also, that we can't just transform into the post-compilation form above, because it's essentially un-roundtrippable. And then there's the issue of getting this
to resolve correctly when a Bar
instance has its foo
called. So instead, we'd really want our post-compilation to look something like:
/// import { FooTrait } from "./FooTrait.xxx" /// export class Bar { constructor() { /* ... */ } } void(`extends`, Bar.prototype.foo = FooTrait.prototype.foo);;
(Note that even if we scuttle this idea, there may be something salvageable from that void-double-colon form that we speculatively introduce above.)
I'm not against, however, something like:
/// import { FooTrait } from "./FooTrait.xxx" /// export class Bar { constructor() { /* ... */ } } $extends$: Bar.prototype.foo = FooTrait.prototype.foo;
... or when mixing in multiple:
/// import { FooTrait } from "./FooTrait.xxx" /// export class Bar { constructor() { /* ... */ } } $extends$: Bar.prototype.foo = FooTrait.prototype.foo; Bar.prototype.fum = FooTrait.prototype.fum;
Whatever the case, if we do something like this, we have ordering constraints (just like with "proper" subclassing) since class
forces in-order, lexical bindings on us (unlike function declarations, which get hoisted).
Alternatively, since we're optimizing for ergonomics, the prescribed pre-compilation form to mix in multiple slots (without having to repeat the FooTrait.
prefix for every one) might have a shorthand that instead looks like:
import { FooTrait } from "./FooTrait.xxx" export class Bar { constructor() { /* ... */ } } +fee (from FooTrait) +fi +fo +fum
(Does that make it start to feel too much like Objective-C/-J? We need to be mindful of trying to maintain offests line-for-line, which constrains how much latitude we have... Plus it needs to be easy to parse.)