8.22.2006
4:25 AM

Mixins define a set of methods that can be "mixed in" to a class definition in order to provide additional functionality. Of course, this often leads to another construct, such as the Ruby Module construct. But Io isn't Ruby.

We don't need a seperate construct for classes, and we certainly don't need a seperate construct for mixins. You can define your mixins just like any other objects, like this one, a clone of the Comparable module from Ruby:

Comparable := Object clone do(
    == := method(other,
        self compare(other) == 0
    )
    < := method(other,
        self compare(other) == -1
    )
    > := method(other,
        self compare(other) == 1
    )
    <= := method(other,
        self compare(other) != 1
    )
    >= := method(other,
        self compare(other) != -1
    )
    isBetween := method(this, that,
        (self > this) and (self < that)
    )
    removeAllProtos  # Remove Object from the mixin. It's going to cause trouble.
)

This mixin provides a number of comparison operators when the target object provides the "compare" method. Now, we just have to make sure that our mixin is on the top of our prototypes list, and therefore first in line for slot lookups. This example creates an object called SizeMatters, which inherits from Sequence, and then prepends the Comparable mixin to its prototypes:

SizeMatters := Sequence clone do(
    prependProto(Comparable)     # Here we prepend Comparable, putting it
                                 # first in line for lookups.
    compare := method(other,
        size compare(other size)
    )
    
    with := method(str,
        str clone setProto(SizeMatters)
    )
)

We can see this working here:

s1 := SizeMatters with("z")
s2 := SizeMatters with("yy")
s3 := SizeMatters with("xxx")
s4 := SizeMatters with("wwww")
s5 := SizeMatters with("vvvvv")

s1 < s2              // true
s4 == s5             // false
s4 isBetween(s1, s3) // false
s4 isBetween(s3, s5) // true

Io uses a form of multiple inheritence to accomplish this. Your new SizeMatters object inherits from Comparable first, and Sequence second. But since Sequence will provide most of the functionality for SizeMatters, we want to list it first, and so we do. Very dynamic.

Oh, and it's not only easier to understand. It also means that you can insert inheritence wherever you want, like in an already-existing object:

Number protos == list(Object)             // true
Number do(
    == := getSlot("==")                   // Overridding Number == is bad.
) prependProto(Comparable)                // Shove Comparable into the dependencies.
Number protos == list(Comparable, Object) // true

4 isBetween(2, 5) println                 // true

Now, isn't that nice? Ruby modules without Modules.