Last time I went over some of the simple tidbits of the type system. Now I'd like to go over some of the... more controversial features; almost all surround Tangent's ability to do multiple inheritance. But first, let's quickly calm those of you out there questioning my sanity with torches and pitchforks.
The original motivation for the language was component based entities in games. One of the great problems there is how to get the components together into stuff that does more than its parts. And how to get those parts working together. Modern languages make it difficult to do simple type composition that re-exposes the behaviors while sharing certain logical concepts. Multiple inheritance is always tried, but quickly disposed with because... well it sucks. It seemed as if multiple inheritance would be a fine solution if it didn't suck, so Tangent aims for that.
One of the most common threads about component based entities is how to share/reuse some components that seem dreadfully common. Position for example. If you have a component that handles movement, it needs to adjust some position. If you have a physics handler too, then it needs that Position; as well as a renderer, AI... Often times the position just gets dumped into the entity itself. Not a huge deal for Position, but rather self defeating on less ubiquitous traits.
Tangent addresses this problem by providing property access to fields, and supporting some syntactic sugar:
public class Moveable{
public abstract Point Position;
public Move(Direction to) => void{ ... }
}
Here, Position looks like an abstract field (and in earlier models, actually was). It is actually sugar to require a read/write property. Since fields expose the property, it allows the programmer to implement the requirement however they like. Better yet it makes things consistent within the language itself.
Moveable then has its dependent component encoded into the type system. As long as it is aggregated with something that implements the abstract property, it will work happily.
Which leads us to some of the common multiple inheritance problems. Tangent has two cases of the Diamond Problem. The first:
public goose class A{}
public goose class B: A{}
public goose class C: A{}
public goose class D: C with B{}
This isn't really multiple inheritance. The 'with' operation creates an anonymous type where C inherits B. The dispatch then proceeds like single inheritance would. To use B's method in certain cases requires you explicitly override it in D and then invoke that version (syntax TBD).
The second:
public class A{}
public class B{} // implements A
public class C{} // implements A
public class D{} // implements B and C
public foo(A:arg)=>void{}
public foo(B:arg)=>void{}
public foo(C:arg)=>void{}
//somewhere in code
local A: d = new D;
foo(d);
Since Tangent supports dynamic dispatch on parameters, the runtime type of d is used to determine what overload to use. Sadly, there's no great solution here. If the local was declared as D you would get a compile time error. With this code, you will get an exception. Static analysis can identify methods that aren't 'closed' as far as the type system is concerned. It'll likely be a compiler flag.
The other common issue is what order to run constructors/destructors/etc. Here is where Tangent takes a little deviation. There are no constructors. Tangent allows only field specific initializers. The inheritor (or the left side of the 'with' operation) wins if there's a collision on non-private fields.
Combined with some of the other language features to accommodate the 'I need some value to initialize the invariant!' use of constructors, this should provide a good mechanism for type compositioning without many of the headaches found in other implementations (but surely a few of its own).
Here's hoping for more frequent work/posts!
No comments:
Post a Comment