Wednesday, March 12, 2008

Update: Inter-type declarations with implicit conversions

With some closure magic it's possible to shorten the code to build a class extension aka. to add some inter type members.
class ClassExtension[S, T](createExtendedObject: => T) {

val extendedObjects = new HashMap[S, T]

implicit def extendObject(o : S) : T = {
if (!extendedObjects.contains(o)) {
extendedObjects += o -> createExtendedObject;
}
extendedObjects(o);
}
}

Instead of making the class abstract and using a factory method on the concrete class, we can use a closure for the construction of the extended / alternative object.

The class can be used like this:
def main(args : Array[String]) : Unit = {
object PositionVertexExtension extends ClassExtension[Vertex, Position](new Position);
import PositionVertexExtension._;
val v = new Vertex("v1");
v.x = 2.3;
println(v.x)
}

This code shows another nice feature of scala. It is possible to have imports scoped to blocks. So in this case the extension is only visible in this local context. This is comparable to Groovy where categories are applied by the use controll structure.

Monday, March 10, 2008

Inter-type declarations with implicit conversions

AspectJ has a feature called inter-type declarations. With this feature you can add methods to an existing class without touching the class itself. (Similar to categories in Groovy) Actually it is even possible to add fields to classes with AspectJ. This is especially useful if you don't want to add the field to the class itself because it is used only by a small subset of all users.

I missed such a feature in Scala. But in fact it is possible to add methods and fields on a class with Scalas implicit conversions. Debasish Ghosh explains how to add methods on existing classes.

But how do we add new fields to a class? E.g. we have a class Vertex. This class is used in severall packages. Now we want to layout some vertices in an euclidean plane. In the context of the layouting algortihm every vertex has a position.
def main(args : Array[String]) : Unit = {
val v = new Vertex();
v.x = 2.3;
println(v.x)
}

It would be nice to use a vertex like this without the need to access a map manually to get the additional fields. In this piece of code object creation is in our control, but we assume that the layouting algorithm does not have control over the vertex creation.

The extension Position would look like this:
class Position {
var x = 0.0;
var y = 0.0;
}

The last missing piece is how to delegate the call to x.
object EuclideanExtension {  
val extendedVertices = new HashMap[Vertex, Position]

implicit def extendVertex(v : Vertex) : Position = {
if (!extendedVertices.contains(v)) {
extendedVertices += v -> new Position;
}
extendedVertices(v);
}
}

The implicit conversion enables us to use all methods and fields of Position whenever we import the EuclideanExtension. (import graph.EuclideanExtension._;)

It is possible to extract most of the code to an abstract, generic class. So you can reuse this extension mechanism.
abstract class ClassExtension[S, T] {  
val extendedObjects = new HashMap[S, T]

implicit def extendObject(o : S) : T = {
if (!extendedObjects.contains(o)) {
extendedObjects += o -> createExtendedObject;
}
extendedObjects(o);
}

def createExtendedObject : T;
}

Using the abstract class reduces the code:
object EuclideanExtension extends ClassExtension[Vertex, Position] {  
def createExtendedObject = new Position;
}

Monday, March 3, 2008

2 or more, use a for

I want to start this post with a citation. Dijkstra coined the phrase "2 or more, use a for". In common use a for loop is an abstraction for iterating through a collection.

In scalax the for keyword is used to build something like the "using" keyword in C#. As expected the following code to copy a file line by line contains three nested iterations:
def createReader = ManagedResource(new BufferedReader(new FileReader("test.txt")))
def createWriter = ManagedResource(new BufferedWriter(new FileWriter("test_copy.txt")))

//copy a file, line by line
for(reader <- createReader; writer <- createWriter) {
var line = reader.readLine
while (line != null) {
writer.write(line)
writer.newLine
line = reader.readLine
}
}

You can find more on how to build an Automatic Ressource Management feature for Scala on Chris Hansen's Blog: Polyglot Window: Chris Hansen's Blog: ARM Blocks in Scala, Part 3: The Concession