Tonight was the monthly BASE meeting. Jorge did a great talk on Scala actors. Before the talk, Dick was talking about looking for a good builder implementation in Scala. This seemed to be an area where Scala did not offer much over Java. Even using some of Scala's more sophisticated syntactic sugar, the resulting builder is not satisfactory. I asked Dick that if Scala had named parameter, would that be good enough?
So I did some playing around with simulating named parameters in Scala. Let's say we have a class like this
class Beast (val x:Double, val y:Double, val z:Double){
// other stuff in here
}
Now suppose that x and y are required, but z can have a default value of 0. My attempt at simulating named parameters involved creating some classes corresponding to the variables.
class X(val x:Double)
class Y(val y:Double)
class Z(val z:Double)
object X{
def ->(d:Double)= new X(d)
}
object Y{
def ->(d:Double)= new Y(d)
}
object Z{
def ->(d:Double) = new Z(d)
}
Do you see where this is going? Next we need a companion object for Beast:
object Beast{
def apply(xyz:Tuple3[X,Y,Z]) = new Beast(xyz._1.x, xyz._2.y, xyz._3.z)
}
Now we can do something like this:
val c = Beast(X->3, Y->4, Z->5)
So X->3 calls the -> method on the X object. This returns a new instance of the X class with value 3. The same thing happens for Y->4 and Z->5. Putting all thee inside the parentheses gives us a Tuple3. This is passed in to the apply method on the Beast object which in turn creates a new instance of Beast with the given values. So far so good?
Now we just need a way to make z optional and give it a default value if it is not supplied. To do this, we need some Evil.
object Evil{
implicit def missingZ(xy:Tuple2[X,Y]):Tuple3[X,Y,Z]=(xy._1,xy._2, new Z(0))
}
Now it is possible to get the optional value behavior:
The implicit def missingZ is used to "invisibly" convert a Tuple2[X,Y] into a Tuple3[X,Y,Z].
object BeastMaster{
import Evil._
def main(args:Array[String]){
val b = Beast(X->1, Y->2)
println(b)
val c = Beast(X->3, Y->4, Z->5)
println(c)
}
}
Unfortunately this is where the coolness ends. You can't switch around the order of the variables, i.e. Beast(Y->2, X->1) or even Beast(Z->5, X->3, Y->4). You can't just add more implicit defs either. Like if you try:
object Evil{
implicit def missingZ(xy:Tuple2[X,Y]):Tuple3[X,Y,Z]=(xy._1,xy._2, new Z(0))
implicit def missingZ2(yx:Tuple2[Y,X]):Tuple3[X,Y,Z] = (yx._2, yx._1, new Z(0))
}
This will cause Beast(X->1,Y->2) to fail to compile. You will get the following error:
error: wrong number of arguments for method apply: ((builder.X, builder.Y, builder.Z))builder.Beast in object Beast
val b = Beast(X->3, Y->5)
This is not the most obvious error. The problem (I think) is that the compiler can't determine which implicit def to use. The culprit is type erasure. There is no way to tell the difference between a Tuple2[X,Y] and Tuple2[Y,X] at runtime. At compile there is, so you would think that it would be possible to figure out which implicit to use... Or perhaps it is possible to merge the two implicit together by using an implicit manifest?
No comments:
Post a Comment