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?
BTW, it looks likely that Scala will get named and optional parameters for 2.8.0. See: http://lampsvn.epfl.ch/svn-repos/scala/lamp-sip/named-args/sip.xhtml
ReplyDeleteIt's planned that Scala will have named parameters in 2.8. You can find a sketchy implementation in one of the branches in svn, I believe.
ReplyDeleteMeanwhile:
trait BeastBits {
. . val x, y, z: Int
}
case class Beast(x: Int, y: Int, z: Int)
object Beast { def apply(bits: BeastBits): Beast = Beast(bits.x, bits.y, bits.z) }
object Main { def main(args: Array[String]) {
. . println(Beast(new BeastBits { val x = 5; val y = 6; val z = 7 })
prints: Beast(5,6,7)
Or a little better for this specific case:
ReplyDeletescala> trait Beast { val x, y, z: Int; override def toString="I am a beast measuring "+x+" by "+y+" by "+z+", so watch it." }
defined trait Beast
scala> new Beast { val x = 4; val y = 5; val z = 6 }
res0: java.lang.Object with Beast = I am a beast measuring 4 by 5 by 6, so watch it.
I think what you are seeing is a bug. Whatever the problem is, it's not type erasure. Implicits are dispatched statically before type erasure occurs
ReplyDeletescala> implicit val x = List(1,2,3)
x: List[Int] = List(1, 2, 3)
scala> implicit val y = List('a', 'b', 'c')
y: List[Char] = List(a, b, c)
scala> def foo(implicit z : List[Int]) = z
foo: (implicit List[Int])List[Int]
scala> foo
res0: List[Int] = List(1, 2, 3)
If type erasure were the problem then foo wouldn't work.
Ricky Clarkson: You mentioned there was a branch that supported named parameters? Which one are you referring to?
ReplyDeleteDaniel,
ReplyDeletehttp://lampsvn.epfl.ch/trac/scala/browser/scala/branches/named-args