Closed javierfernandes closed 8 years ago
Related pending issues #536, #537, #538, #540, #541
This is implemented in branch dev-mixins which is based on dev-splitting-plugins
Another point to discuss is whether the required methods for a mixin must be explicitly declared as abstract methods in the mixin or just inferred by its usage. For example:
Implicitly
mixin M {
method foo() {
this.bar()
}
}
Explicitly
mixin M {
method foo() {
this.bar()
}
method bar()
}
It currently works implicitly
@npasserini @tesonep @flbulgarelli A couple of things to discuss here about mixins.
I have a current working solution in #dev-splitting-plugins branch (yeah, sorry, I need to refactor that branch, merging the split-plugins feature into dev, and then "renaming" the branch to "mixins"). Or cherrypick the mixins to a new branch from dev to decouple the changes. Damn I would really love to have a browser based version of wollok to have continuous deployment to try-out features in branches live :(
The impl es basically based on Scala mixins, which I think are pretty simples but powerful (it actually didn't involved much changes to the method dispatch and is fully transparent if you use just classes without mixins) Some other languages model mixins as extensions but they don't specify much on how to handle method overrides and things like stackable pattern.
The first part of this issue describes the current behavior. Then the linked issues, plus the "Limitations" opens questions on a couple of features that are not implemented.
I have found that to make it easier checking object instantiation it would be good to have mixins required methods explicitly declared, like this
mixin FullName {
method getFullName() = this.firstName() + ", " + this.lastName()
method firstName() // requirement
method lastName() // requirement
}
Instead of just
mixin FullName {
method getFullName() = this.firstName() + ", " + this.lastName()
}
However if you think that we really should have the implicit implementation I can do a refactor to implement it.
Some comments:
Some more comments:
mixed with
is a bit verbose, and I don't think we use that term in our speech. Actually there is not a good translation into spanish for it. Instead, I think the most common term for introducing a mixin in a hierarchy is "inclusion" requirement
and not just a comment - side note: do we have an abstract
keyword for explicit abstract methods?So, instead of writing
mixin FullName {
method getFullName() = this.firstName() + ", " + this.lastName()
method firstName() // requirement
method lastName() // requirement
}
class Foo inherits Bar mixed with FullName
I suggest the following syntax:
mixin FullName {
method getFullName() = this.firstName() + ", " + this.lastName()
required method firstName()
required method lastName()
}
class Foo inherits Bar includes FullName
Ok to not allow a mixin to be mixed in.
You mean ok to the fact that a mixin cannot "inherit" from a class neither include (be mixed with) another mixins, right ?
I wonder why Scala uses this feature. I also think that it is difficult to grasp and confuses a lot. I haven't needed it yet in any example/test that I wrote.
Regarding "mixed with" I don't mind being verbose. On the contrary I have been reading a couple of other languages syntax that support mixins and they all use different terms, and none of them seemed expressive enough for me. I don't like Ruby's "include". Reminds me of "includes" from many other languages, like if it was an import or inlining.
I think that "mixed with" follows the core idea of the "mixin" that gets "mixed" in the hierarchy. Maybe "mixing" ?
class A inherits (from) B mixing M { }
Regarding method requirements, I actually thought about modelling them with something like
requires firstName()
requires lastName()
But then I thought that it would add a new concept or somehow confuse. In some scenarios this methods behaves in the same way as any other abstract method (if you mix this you need to provide an implementation). The only difference with a regular "abstract method" in a class is that the implementation here could be down or up the hierarchy chain in this case.
Anyway, I guess that I have like "encountered feelings" about this. I like being expressive and if we think of this methods as requirements then have a keyword for that. But I liked the simplicity of treating them as regular methods.
Ok...okok.. lets add the keyword. :smile: Just that I guess the right one is require and not required
Every time I wrote inherits I feel that we should go ahead and add the "from" too, to make it nicely readable in english. Is kind of weird now.
Oh, we don't have an explicit abstract keyword, neither for classes nor methods. Again I like the fact that it requires less to write, but I agree that we are missing the opportunity to explicitly express there the intention. I think that @npasserini liked the "without abstract" keyword approach. I don't really mind adding it (we should add checks and quickfixes in that case)
Ok to not allow a mixin to be mixed in.
You mean ok to the fact that a mixin cannot "inherit" from a class neither include (be mixed with) another mixins, right ?
Right
Regarding "mixed with" I don't mind being verbose. On the contrary I have been reading a couple of other languages syntax that support mixins and they all use different terms, and none of them seemed expressive enough for me.
OK. Verbosity is not bad by itself. You have a point there. But
inherits
: third person present simple verb. But really, let's get into spanish. Do we say "incluir"? "mezclar"? "extender"? I know I use the first term. What term do you use?
In English I think I tend to use include or mix-in. But mixing in a mixin is tongue twister :disappointed:
Oh, I think something like requires
instead of required method
could also work.
Regarding operational semantics of abstract
and required
methods, yeah, they are nearly the same at implementation level, but they are different concepts and we tend to present them different. That is why I think it is a good idea to have different keywords for them.
If we introduce abstract
keyword, I would suggest to be consistent with the usage of an adjective in both cases: abstract and required. Otherwise, as stated in my previous comment, require(s) is ok.
That way the declaration is consisten with speech:
BTW, we are going to introduce this feature in 1.4. We can postpone syntax discussion
Mixin
A mixin is a new construction similar to a class but:
Some other technical details
Simplest mixin
Here is the simplest mixin providing a new method
Then it can be mixed in a class
And later used in a program/test/library
Mixin with state
Besides behavior (methods), a mixing can define instance variables.
Then used
Instance Variables Access
Instance variables declared by a mixin can be accessed from the the class it is mixed in into.
Mixing multples mixins
A class can mix more than one mixin.
The list of mixins can be separated either with "and" or with a comma character.
Abstract Mixins
A mixin can be abstract if it calls a method which is not defined in itself. Here is an abstract Mixin providing flying capabilities
The method that it needs is reduceEnergy(meters).
When executed "this.reduceEnergy()" will send a message to the current object (this). This means that the method can be anywhere in the object's hierarchy and will be looked up for.
There are 3 possible cases
Method implemented in the class
Method implemented in a superclass
Here we can see that the new method lookup doesn't affect regular class-based inheritance.
Method defined in another mixin
It could be in a new mixin
And then used
The mixin order in this case is not relevant, since sending a message to this starts a new method lookup that starts bottom-up from the object's concrete type. We will see later that the declaration's order indeed is important for method look up in other cases
Linearization
Mixins are hooked up into the class hierarchy between classes following a linearization algorithm (This is basically the same way Scala traits/mixins works) This mechanism makes sure that there are no complex hierarchy graphs. It all ends up being a linear relation between classes and mixins. So the hierarchy is a simple List and the method and variables lookup mechanism only has an predictable flow up the chain with a simple step and not a split decision to take.
Here are some example linearizations
B's hierarchy ends like this
If we add a new mixin:
The new hierarchy would be
**Notice here that the declaration order DOES MATTER. Mixins on the right side are lower in the hierarchy. Meaning that they have precedence over the ones on the left
Having mixins up in the class hierarchy doesn't complicate this at all, since each classes mixins are resolved in the same way
D's inheritance chain is
This was resolved class by class
Method Override
Understanding linearization is important to implement modular mixins which collaborate between each others without knowing themselves. We have seem some simple cases already. Here we present more complex cases where a class/mixin overrides a method
Class overrides mixins method
The class on which we are mixing the mixins has the biggest priority since it is the lower end of the hierarchy, that means that it can override a method defined in a mixin. In the same way that a class can override a method defined in a super class
Given this mixin
A class can be mixed and override the "reduceEnergy(amount)" method
Super call (in a class overriding a mixin method)
As with any method overriding a method in a super class its body can use the super keyword to execute the original method being overriding.
Super call in mixin
This is the most complex case and also the most flexible. A mixin can override a method and also use the original implementation. For that case the super works like a "dynamic dispatch" (just "like"). Because looking at the mixin code that calls super() is not enough to statically understand which method will be called by that "super" call. For example:
We don't know what "super" means in this context. But we do know once the mixin is combined (statically) into a class
Given this class
And this mixup
We now know that the "super" call in the mixin will call the "doFoo(chain)" method defined in the C1 class (C2's super class).
Here is the sample
Stackable Mixin Pattern
This means that the original method being overriden can be actually defined in another Mixing up in the hierarchy. And that mixin in turn could be overriding just to call super. So is like every mixin "decorates" or alters the original method behavior by reusing it, getting itself in the middle.
This is called the "stackable mixin pattern", because mixins can be combined by stacking them much like a chain of responsibilities pattern.
Here is the same example as before but with a couple more mixins
And then the classes
Executing this code
Prints the following
Which is basically the linearized hierarchy
Object's Mixins
Named objects can also be combined with mixins
In this case the inheritance chain will be:
Limitations
Mixin inheritance
Current mixins implementation doesn't support a mixin extending another mixing or class. That's a difference from Scala mixins.
Native mixins
What if a mixin wants to implement a method natively ?
Combining mixins at instantiation time
This can be done in 1.6 !