10 comments:

I prefer the flexibility you get with a structural type system. If I've got a duck, why should I care if it inherits from DuckBase or implements IDuck (unless I'd like to inherit some default functionality)? All I should have to care about is whether it has a Beak and can Quack().

@Ehren That isn't quite how structural typing works. Duck typing is really the absence of a type system - everything is totally late-bound. If the object has all the necessary members, awesome. Otherwise, epic failure ensues, especially when it comes to functions with side effects.

Structural typing is quite different. Objects have specific types, and so do l-values (variables, fields, and parameters). The only difference from an ordinary static type system is that an object's type is solely defined by what members that type specifies. This means that any value can be assigned to any l-value as long as the value's type is a superset of the l-value's type. This means that a duplicate of any type is considered to be exactly equivalent, unlike, say, C#.

This system is pretty nifty, but it falls apart when it comes to generating interfaces for other languages to M-based repositories. The concept of structural typing is totally incompatible with .NET, and this pretty much destroys any opportunity for end-to-end modeling (from database to application), throwing away one of the key benefits of modeling.

I see you are interested in the opinions of programmers, not modellers ;)

For model-driven applications where models are executed directly without code generation and compilation, where models will evolve during execution, conventional type checking should be replaced by user-defined business rules to enforce constraints. You should e.g. be allowed to state Donald : Duck; Donald : CartoonCharacter;unless there is a business rule that states that the two types are disjoint.

What modellers need is incremental value construction, sometimes called feature composition, where new types may be added to an instance dynamically, resulting in the addition of missing type features to the instance.

Alongside nominal typing, the single instantiation class constraint of OO is a major barrier to this, in M as well. When all features of an object must be defined at the time of its creation, there is no evolution.

You should in some cases also allow users to remove a type from an instance, e.g. to reflect that "Pluto is no longer a planet".

Sometimes, you should even tolerate inconsistencies, in order to reflect the uncertainty of the modelled information, e.g. if Pluto is defined as a Dog in one module and a Planet in another, we cannot immediately assume which interpretation applies in a third context. Of course, in a robust model-driven environment, the errors are not epic disasters, you can always push the problem up to the user/modeller with rather simple error messages.

Encapsulation is the final language constraint that should be relaxed in model-driven execution. In some cases it is perfectly reasonable to ask any object to Quack like_a Duck; Walk like_a Duck;even though it is not a duck, as long as the behaviour can be delegated to a Duck implementation available in the execution context. Side effects could lead to addition of features to the object, which is probably what the modeller/user wanted.

I guess the key question is which use cases you want M to handle directly, in the spectrum from .Net code generation to completely user controlled model-driven applications. Though M takes some steps in the right direction with dynamic typing, there are still a number of constraints that I will have to code around if I decide to use Oslo as a platform for writing my own model interpreters/activators/executors. M currently takes a middle-of-the-road approach, probably not satisfying any of the ends of the spectrum. An alternative would be to define an agnostic language that lets the implementers of runtime model interpreters decide what kind of typing model and other language constraints they would like to apply.