This is a purely just "playing-around" post.
In Uqbar Project we are now using several new languages both as part of teaching programming in a couple of universities, but also as one of our main/many languages of development for research projects.
But I'm not going to bore you with that right now. Probably this will be the start of a series of post related to this language (an why not some other regarding scala and groovy) and rethinking our programming practices and design decisions with its particular mechanisms.
At the end maybe some kind of summary post.
Anyway back to the original point then I wanted to try out extension methods.
But not just adding methods to a class. That's easy. Beyond just methods, OOP most important feature is polymorphism. So, how extension methods relates to polymorphism ??
Then, wait, maybe you are wondering what's this extension method thing ?
Extension Methods Explained
For those who are not familiar with the term extension methods is a language-level support for defining new behavior (methods) for existing classes, without actually touching the original classes.
And of course, then use those as regular methods.
Usually this takes the form of a method whose first parameter is the target object. Belonging to the class we are extending.
For example you could add a "toCamelCase" method to the String class with something like this:
def String toCamelCase(String aString) {
// ....
}
And then call it on any string:
println("Hi there".toCamelCase())
There are several different implementations of this concepts in many languages.
But they are not always exactly the same and don't give you the same posibilites (power)
For example C# "Extension Methods" can only be defined as static methods.
Adding polymorphic Extension Methods
So enough of introduction. Maybe we will create a separated post to compare the different extension method mechanisms in several languages.
And we could create another post just to talk about xtend's extension methods.
Adding a method to a class is great as you have seen to add some kind of "atomic" functionality. Like all utility classes that we use like CollectionUtils, ReflectionUtils, ListUtils, etc.
Now, can we use it to add polymorphic behavior ??
The short answer is ... YES !
Long answer: extension methods + multimethods
This can be achieved by combining extension methods with multiple dispatch, another nice feature of xtend.
Remember that the target object is the first parameter of the extension method.
Also remember that multiple dispatch is the ability to select the method implementation based on the actual type of the arguments.
So they complement each other quite well.
We can create an extension method, which is actually a set of different methods one for each target type (first argument).
Then call it as if it was a regular method with different implementations on each class. Like a regular polymorphism with classes overriding.
And here's a piece of code that demonstratse it.
Suppose we have some kind of CandyCrush object model.
There are Candy's and different classes for Movements (Move): to go North (move up a candy), go South, East, and West.
class Move {
//...Some behavior here
}class North extends Move { /* ... */ }class South extends Move { /* ... */ }class East extends Move { /* ... */ }class West extends Move { /* ... */ }
A candy has a coordinate and knows how to move itself a delta (x-delta, and y-delta)
class Candy {
@Property Pair<Integer, Integer> coordinates;
def move(int deltaX, int deltaY) {
coordinates =
coordinates.key + deltaX -> coordinates.value + deltaY
}
}
Suppose the "Move" class don't actually have a method to move a candy (ok, sounds silly, but just for the sake of the example).
We will like to add a new method ----> Ahá !, Extension methods !!
But wait, each Move subclass will have a different implementation ! We need polymorphism !!! -----> Ahá!, Multiple dispatch !
So we will like to do this:
def static void main(String[] args) {
val candy = new Candy => [ coordinates = (0 -> 0) ]
new Use().moveIt(candy)
}
def moveIt(Candy candy) {
#[new North, new East, new South, new West]
.forEach[
m| m.moveCandy(candy)
println(candy.coordinates)
]
}
The "moveCandy" is the actual polymorphic method that we want to add.
And it's defined in an extension class:
class CandyExtensions {
def dispatch void moveCandy(North north, Candy candy) {
candy.move(0, 1)
}
def dispatch void moveCandy(South north, Candy candy) {
candy.move(0, -1)
}
def dispatch void moveCandy(West north, Candy candy) {
candy.move(-1, 0)
}
def dispatch void moveCandy(East east, Candy candy) {
candy.move(1, 0)
}
}
Eclipse's tooltip helps to see this:
Regular solution without extension methods
So even if we coded several methods (4), conceptually we have just added one method and several implementations for concrete types.Without extensions it would be something like this:
class Move {
def void moveCandy(Candy candy)
}class North extends Move {
override void moveCandy(Candy candy) {
candy.move(0, 1)
}
}class South extends Move {
override void moveCandy(Candy candy) {
candy.move(0, -1)
}
}class East extends Move {
override void moveCandy(Candy candy) {
candy.move(1, 0)
}
}class West extends Move {
override void moveCandy(Candy candy) {
candy.move(-1, 0)
}
}
Not it's actually 5 methods, 4 impl + the abstract declaration in Move
Limitations
That sounded great !! I can add polymorphic method to existing classes.But can I do it to any class ?
Can I, from the outside, make two different classes polymorphic in respect to a new method ?
Yes, you can, but it will have some drawbacks.
In our example we added a polymorphic behavior to classes which already where polymorphic as part of being in the same hierarchy.
But what if they weren't ? What if they were just completely different classes with no hierarchy ?
Let's remove the Move class:
class North { /* ... */ }class South { /* ... */ }class East { /* ... */ }class West { /* ... */ }
Well there's still a multiple-dispatch method. That's fine.
It's just that it applies to any Object.
Mmm... that's not good, but, well.. at least it's there and it still can be used in our main !
But what then if we call it with something else:
def moveIt(Candy candy) {
#[new North, new East, new South, new West, new Banana]
.forEach[
m| m.moveCandy(candy)
println(candy.coordinates)
]
}
Of course.. boom !
Exception in thread "main" java.lang.IllegalArgumentException: Unhandled parameter types: [org.uqbar.xtend.playground.extensionpolymorphism.Banana@51d92803, org.uqbar.xtend.playground.extensionpolymorphism.Candy@7d206f0]
at org.uqbar.xtend.playground.extensionpolymorphism.Extensions.moveCandy(Extensions.java:44)
at org.uqbar.xtend.playground.extensionpolymorphism.Use$3.apply(Use.java:54)
at org.eclipse.xtext.xbase.lib.IterableExtensions.forEach(IterableExtensions.java:399)
at org.uqbar.xtend.playground.extensionpolymorphism.Use.moveIt(Use.java:59)
at org.uqbar.xtend.playground.extensionpolymorphism.Use.main(Use.java:43)
So, in conclusion, you could add polymorphic methods to unrelated classes, but as you won't have any common type to bound it with, it will kind of confusing to use, and you'll need to be careful when using it. Kind of what happens with languages with run-time message checking (instead of compile-time)
Further Questions and Relation to other Concerns
AOP
An advantage of the polymorphic extension methods approach is that of course, it allows to add that behavior from outside of the class. Sometimes you cannot change the original classes.But also another one (that could also be seen as a disadvantage) is that it groups together all the "move" behavior into a single place. Kind of a concern applied in AOP.
Visitor
This effect is also similar and sometimes part of the intention also present in the Visitor Pattern. Where the visited class only knows how to transverse each other in a general algorithm, then each visitor implements the actual logic for each visited class in a custom method. In order for this to work you need to simulate multimethods with double-dispatch.
So what happens with the visitor pattern now that we have multimethods, but also we can combine them with extension methods to actually "inject" the polymorphic behavior ?
Does it still make any sense ?
Structural Types and Duck-Typing
Last, we have saw that you can use this mechanism even for unrelated classes, but then you end up using Object, because you can add methods from outside, but you cannot change the type hierarchy or force classes to start implementing interfaces (like for example in AOP with weaving).
Related to this, there languages like Scala which implement a duck-typing in a strongly typed environment (checks on compile-time). These are a form of StructuralTypes.
Through extensions we have actually defined some kind of new "structural type". A new type which represents objects which can "move a candy".
Why should we type any other method that receives such an object via parameter with "object" ?
Would it make sense (or be possible) to introduce new types, using the extension type itself as a nominal type on top of the structural type defined by the extension methods ?
Would it be possible to combine ext.methods + multiple-dispath + duck typing ?
If you check our Extension class after we've removed the hierarchy you'll see that there's enough information for a clever compiler to see our modified main and know that it will fail !
It just needs to inspect all the dispatch methods to know which types are valid and which one are NOT !
Banana isn't a valid type.
This opens up a new world to explore !! :)
Different Extensions implementations
Extensions don't need to be static methods (as in C#) therefore they are implemented in instances of a class. Therefore, into an object.
But then you just call with the first arg as target.
Eventually there's an automatic delegation or dispatch to the method into the extension object instance that you have (from the piece of code you are calling it).
The extension itself is an object and a regular class, therefore you could create a hierarchy an use polymorphism, having different sets of implementations for the extension themselves.
Here's a silly new implementation for our extensions introducing a hierarchy:
abstract class AbstractExtensions {
def void moveCandy(Move north, Candy candy)
}
class ExtensionImpl extends AbstractExtensions {
def dispatch void moveCandy(North north, Candy candy)
candy.move(0, 1)
}
def dispatch void moveCandy(South north, Candy candy) {
candy.move(0, -1)
}
def dispatch void moveCandy(West north, Candy candy) {
candy.move(-1, 0)
}
def dispatch void moveCandy(East east, Candy candy) {
candy.move(1, 0)
}
}
class InversedExtensionImpl extends AbstractExtensions {
def dispatch void moveCandy(North north, Candy candy) {
candy.move(0, -1)
}
def dispatch void moveCandy(South north, Candy candy) {
candy.move(0, 1)
}
def dispatch void moveCandy(West north, Candy candy) {
candy.move(1, 0)
}
def dispatch void moveCandy(East east, Candy candy) {
candy.move(-1, 0)
}
}
Then in our main or piece of code that actually uses the moveCandy method we can control the real implementation of the set of methods:
class Use {
var extension AbstractExtensions myExtensions = new ExtensionImpl
def moveIt(Candy candy) {
#[new North, new East, new South, new West]
.forEach[
m| m.moveCandy(candy)
println(candy.coordinates)
]
println("----------: new extension impl")
//change IT !
this.myExtensions = new InversedExtensionImpl
#[new North, new East, new South, new West]
.forEach[
m| m.moveCandy(candy)
println(candy.coordinates)
] }
/* ... main ...*/
}
In this case we have it just hardcoded there in the initialization. But the extension instance could be injected. Also note that we are also changing the extension instance with the new one.
This prints:
0->1
1->1
1->0
0->0
----------: new extension impl
0->-1
-1->-1
-1->0
0->0
Therefore just by changing the reference to the extension (like if it was a prototype's parent object) we change the implementation of all methods that affects several classes (North, South, etc). Also kind of changing an aspects implementation.
No comments:
Post a Comment