Mantra mixins behave like Ruby mixins
Mixins behave like abstract superclasses except that a class may include more than one mixin (very much like multiple inheritance). Recall that abstract superclasses add functionality to subclasses and may refer to methods that don't have implementations in the abstract superclass. A subclass "includes" all of the methods and fields from the superclass except for those methods overridden in the subclass. A mixin differs only in that you do not have to provide abstract method declarations for undefined methods you call.
Philosophically, mixins differ from abstract base classes in an important way: mixins are just code and do not imply anything about identity. The superclass/subclass relationship, in contrast, is normally one of identity ("is a" relationship). For example, a manager "is an" employee and a house cat "is a" feline (well, some people think they are human). In contrast, a string is not a kind of Comparable object, but it does want to reuse comparison operators/code. Mixins in mantra provide the ability to share code without polluting the class hierarchy.
My desire for something like mixins came up when I realized that the exact same code for stream comparison should be included in all input streams. How do you do that without sharing the base class or duplicating code? After rolling back two other mechanisms designed to avoid duplicating code in the output, I settled on a simple include-like mechanism. The method bodies of mixin methods must be evaluated in the context of the class that includes them. It was just plain easier to reevaluate mixin methods in the proper context. The programmer still has a single method to change, but the generated code will see some duplication.
Mixins are valid type names in mantra because they are translated to interfaces in the output Java code. Mantra classes use the implements keyword as if the mixins it were interfaces. I was using includes but it didn't read as nicely. For example,
class Words implements Actor {...}
reads much better than "... includes Actor". Just doesn't make as much sense. I guess mixins are like interfaces that bring in some default implementation.
Here is an example mixin, taken from Ruby, that adds comparison operators to a class:
mixin Comparable {
alias "<" = "lt1"; // alias is not meant for use by end-user
alias "<=" = "le1";
alias "==" = "equals1";
alias ">=" = "ge1";
alias ">" = "gt1"";
boolean le(object r) { return this.compareTo(r) <= 0; }
boolean lt(object r) { return this.compareTo(r) < 0; }
boolean ge(object r) { return this.compareTo(r) >= 0; }
boolean gt(object r) { return this.compareTo(r) > 0; }
boolean equals(object r) { return this.compareTo(r) == 0; }
boolean notEquals(object r) { return this.compareTo(r) != 0; }
}
This mixin relies on method compareTo; all classes that include it must implement that method. Imagine you want to compare User objects. Rather than add operator methods to User, include Comparable:
class User implements Comparable { string name; User(string name) { this.name = name; } int compareTo(User o) { return name.compareTo(o.name); } toString() { return name; } }
Here's a sample usage:
list users = [User("Ter"), User("Kay"), User("John")]; if ( users[0] > users[1] ) println("out of order"); users.sort(); // uses compareTo println(users); // emits [John, Kay, Ter] if ( users[0] <= users[1] ) println("in order");
The output is:
out of order [John, Kay, Ter] in order
Classes only pull in those methods from mixins that are not defined directly in that class. Locally defined methods override those from mixins. In a sense classes override mixin methods.
Classes only pull in those fields from mixins that are not defined directly in that class.
If you include more than one mixin and method foo is defined in both, the foo from the first mixin is used. A "lint" tool could provide warnings.
One of the more useful mixins is InputStream that defines not only the basic interface but also some very useful support code: the equals method:
package mantra::lang; mixin InputStream { alias "==" = "equals1"; open() {;} close() {;} object read() { return null; } object toInputStream() { return this; } list toList() { return java {new mlist(streamToList(this))}; } boolean equals(object o) { InputStream stream = o.toInputStream(); this.open(); stream.open(); char x = this.read(); char y = stream.read(); while ( x isnt EOF && y isnt EOF ) { if ( x!=y ) { stream.close(); this.close(); return false; } x = this.read(); y = stream.read(); } stream.close(); this.close(); return true; } }
You cannot make all streams derive from a single base class. Mixins allow them all to share code; InputStream provides the equals method to all of the following classes:
java_iterator_input_stream.om set_input_stream.om list_input_stream.om string_input_stream.om list_range_input_stream.om string_range_input_stream.om list_reverse_input_stream.om string_reverse_input_stream.om string_subset_input_stream.om range_input_stream.om sublist_input_stream.om
Comments (1)
Aug 13, 2008
Anonymous says:
You know Sather? It is a formerly promising but now sadly abandoned strongly-typ...You know Sather? It is a formerly promising but now sadly abandoned strongly-typed OO language with generics. It pushes even further the idea of code inheritance ("inclusion" in Sather parlance) vs. subtyping as two orthogonal concepts. In Sather you can not only choose what to inherit on a member-by-member basis, but also rename inherited stuff in the scope of the inheriting class! This includes the ability to rename features to nothing at all, i.e. remove them (you will have to implement them though, to comply with the interface). In addition there is a language construct called "partial class", a class that is explicitly marked as incomplete and designed to be "included" by other classes - i.e. "mixin".
Check it out: http://en.wikipedia.org/wiki/Sather
Add Comment