Question

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

Was it helpful?

Solution

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)(_ * _)      

OTHER TIPS

Is the error because the compiler doesn't know if B type will be addable ?

Yes. The type parameter B has no constraints in your example - you could use any type as the argument when you use this function. The compiler has no way to check in advance that whatever type you use has a + method.

You can use a type class approach, here's something to give you an idea:

// This defines a generic 'plus' method
trait Plus[T] {
  def plus(x: T, y: T): T
}

// This is a generic method that can do 'plus' on whatever type
// for which there is an implicit Plus value in scope
def add[T : Plus](a: T, b: T) = implicitly[Plus[T]].plus(a, b)

// This defines what 'plus' means for Ints
implicit val intPlus = new Plus[Int] {
  def plus(x: Int, y: Int): Int = x + y
}

// This defines what 'plus' means for Strings
implicit val stringPlus = new Plus[String] {
  def plus(x: String, y: String): String = x.concat(y)
}

// Examples of use
println(add(2, 3))
println(add("hello", "world"))

edit: Actually, Scala's Numeric already does this for you:

def add[T : Numeric](x: T, y: T) = implicitly[Numeric[T]].plus(x, y)

// Use it with integers
println(add(2, 3))

// Or doubles
println(add(1.5, 2.4))

// Or with for example BigDecimal
println(add(BigDecimal("1.234"), BigDecimal("4.567")))

So you should be able to do something like this:

def defaultParamFunc[B : Numeric](z: B, y: B)(f: (B, B) => B = (v1: B,v2: B) => { implicitly[Numeric[B]].plus(v1, v2) }) = {
  val ans = f(z,y)
  println("ans : " + ans)
  ans
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top