0

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

2 Answers 2

2

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)(_ * _)      
Sign up to request clarification or add additional context in comments.

4 Comments

ok. I tried your code. It's not compiling. When I use defaultParamFunc(2,3)() It gives the error 'Unspecified value parameter magnet'. when I tried defaultParamFunc(2,3){(x:Int, y:Int) => x*y }, I got the error 'found : (Int, Int) => Int required: test.ScalaTest.MyMagnet[Int] Note: implicit method fromUnit is not applicable here because it comes after the application point and it lacks an explicit result type'
Well it does compile (tested in the REPL, scala version 2.10.1). In the REPL you just have to take care to evaluate the MyMagnet trait and the MyMagnet object together, otherwise the latter won't be seen as the companion object of the former. However I can see from the error message that you are not using the REPL. All I can suggest is that you post the exact code (as in "the whole source file(s)") so I can see what is wrong.
Sorry. I am new to scala. I am not using REPL. I am running it using SBT. My scala version is 2.10.3. Here is the file : pastebin.com/HDgprLZD
OK.. now it's working.. silly mistake.. Thanks a lot for the detailed explanation. It was very informative. :)
0

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
}

2 Comments

This does not compile (please see my answer)
Thanks for your response. The first example worked but using the numeric code, I got the error 'could not find implicit value for parameter e: Numeric[B]' in the defaultParamFunc function. the add function worked

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.