I was learning and experimenting with Scala. I wanted to implement a function with generic type, which takes a function as a parameter and provides a default implementation of that function..
Now when I try it without the generic type, it works :
def defaultParamFunc(z: Int, y: Int)(f: (Int, Int) => Int = (v1: Int,v2: Int) => { v1 + v2 }) : Int = {
val ans = f(z,y)
println("ans : " + ans)
ans
}
this doesn't give any error
but when I try the same with generic type,
def defaultParamFunc[B](z: B, y: B)(f: (B, B) => B = (v1: B,v2: B) => { v1 + v2 }) = {
val ans = f(z,y)
println("ans : " + ans)
ans
}
I get the error :
[error] found : B
[error] required: String
[error] def defaultParamFunc[B](z: B, y: B)(f: (B, B) => B = (v1: B,v2: B) => { v1 + v2 }) = {
[error] ^
Is the error because the compiler doesn't know if B type will be addable ? because when I just return v1 or v2 instead of v1 + v2, it works..
def defaultParamFunc[B](z: B, y: B)(f: (B, B) => B = (v1: B,v2: B) => { v1 }) = {
val ans = f(z,y)
println("ans : " + ans)
ans
}
If so, How to specify that the type given must be Numeric ? I tried replacing B with B : Numeric but still gives the same error
The first problem with your code is a minor omission: you need to import the members of the Numeric
instance, so as to bring +
into scope (this will in fact bring num.mkNumericOps
into scope, enabling the +
method):
Let's try this:
def defaultParamFunc[B](z: B, y: B)(f: (B, B) => B = (v1: B,v2: B) => { v1 + v2 })(implicit num: Numeric[B]) = {
// Brings `+` into scope.
// Note that you can also just import Numeric.Implicits._ for the same effect
import num._
val ans = f(z,y)
println("ans : " + ans)
ans
}
Unfortunately this still does not compile. The problem here is that a parameter's default value can only reference a parameter from an earlier parameter list.
Because num
is in the last parameter list, there is no way to use it in a default value.
Tough luck.
OK, so let's try to outsmart the compiler and split the method in two, so that we can have the implicit parameter num
before the parameter f
:
def defaultParamFunc[B](z: B, y: B)(implicit num: Numeric[B]) = new {
// NOTE: using structural typing here is rather inefficient.
// Let's ignore that for now.
import num._
def apply(f: (B, B) => B = (v1: B, v2: B) => { v1 + v2 }) = {
val ans = f(z,y)
println("ans : " + ans)
ans
}
}
Sweet, it compiles. What we have done here is that defaultParamFunc
actually returns a (pseudo) function instance. This function is what takes f
as parameter,
and because we instantiate the function in defaultParamFunc
body there is no problem referencing num
.
However, let's not rejoice too soon. If we try to call it, without specifying the f
parameter, the compiler is not happy:
scala> defaultParamFunc(5, 7)()
<console>:17: error: not enough arguments for method defaultParamFunc: (implicit num: Numeric[Int])((Int, Int) => Int) => Int{def apply$default$1: (Int, Int) => Int}.
Unspecified value parameter num.
defaultParamFunc(5, 7)()
Oops. The compiler thinks that the empty parameter list is for the second (implicit) parameter list in defaultParamFunc
, rather than in the parameter list of the (pseudo) function instance.
We'll have to find another way that does not require an implicit parameter in the defaultParamFunc
method. Magnet patterns to the rescue!
trait MyMagnet[B] extends ((B,B) => B)
object MyMagnet {
implicit def fromFunc[B]( f: (B, B) => B ) = new MyMagnet[B] {
def apply(z: B, y: B): B = {
val ans = f(z,y)
println("ans : " + ans)
ans
}
}
implicit def fromUnit[B](unit: Unit)(implicit num: Numeric[B]) = new MyMagnet[B]{
import num._
def apply(v1: B, v2: B): B = { v1 + v2 }
}
}
def defaultParamFunc[B](z: B, y: B)(magnet: MyMagnet[B]): B = magnet(z, y)
Granted, this is a bit contrived for such a tiny result. But it does work:
scala> defaultParamFunc(2,3)()
res15: Int = 5
scala> defaultParamFunc(2,3){(x:Int, y:Int) => x*y }
ans : 6
res16: Int = 6
Note though that by using the magnet pattern, we have lost the benefits of type inference. So we cannot just do the following:
scala> defaultParamFunc(2,3)(_ * _)
<console>:18: error: missing parameter type for expanded function ((x$1, x$2) => x$1.$times(x$2))
defaultParamFunc(2,3)(_ * _)