6

I followed the advice found here to define a function called square, and then tried to pass it to a function called twice. The functions are defined like this:

 def square[T](n: T)(implicit numeric: Numeric[T]): T = numeric.times(n, n)
 def twice[T](f: (T) => T, a: T): T = f(f(a)) 

When calling twice(square, 2), the REPL spits out an error message:

scala> twice(square, 2)
<console>:8: error: could not find implicit value for parameter numeric: Numeric[T]
       twice(square, 2)
         ^

Anyone?

Community
  • 1
  • 1
Wilfred Springer
  • 10,869
  • 4
  • 55
  • 69

4 Answers4

21

I disagree with everyone here except Andrew Phillips. Well, everyone so far. :-) The problem is here:

def twice[T](f: (T) => T, a: T): T = f(f(a))

You expect, like newcomers to Scala often do, for Scala's compiler to take into account both parameters to twice to infer the correct types. Scala doesn't do that, though -- it only uses information from one parameter list to the next, but not from one parameter to the next. That mean the parameters f and a are analyzed independently, without having the advantage of knowing what the other is.

That means, for instance, that this works:

twice(square[Int], 2)

Now, if you break it down into two parameter lists, then it also works:

def twice[T](a: T)(f: (T) => T): T = f(f(a))
twice(2)(square)

So, basically, everything you were trying to do was correct and should work, except for the part that you expected one parameter to help figuring out the type of the other parameter (as you wrote it).

Community
  • 1
  • 1
Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
  • 1
    +1. Nice: "it only uses information from one parameter list to the next, but not from one parameter to the next" – pedrofurla Jan 04 '11 at 12:41
  • thanks for the clear explanation. So type inference is indeed independent for parameters within a list...but, erm, why..? ;-) I should add that that's not an entirely serious question, as I suspect a thesis-length answer might lurk here. – Andrew Phillips Jan 04 '11 at 17:05
  • @Andrew I guess performance. It's hard enough to infere one type -- what would be required here would be to _unify_ two types and then come up with its inference. That's what languages with full type inference do, but, according to Odersky, it is a hard problem to do it right with languages with a type system on par with Scala's. – Daniel C. Sobral Jan 04 '11 at 17:57
4

Here's a session from the Scala REPL.

Welcome to Scala version 2.8.0.final (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_20).
Type in expressions to have them evaluated.
Type :help for more information.

scala> def square[T : Numeric](n: T) = implicitly[Numeric[T]].times(n, n)
square: [T](n: T)(implicit evidence$1: Numeric[T])T

scala> def twice2[T](f: T => T)(a: T) = f(f(a))
twice2: [T](f: (T) => T)(a: T)T

scala> twice2(square)(3)
<console>:8: error: could not find implicit value for evidence parameter of type
 Numeric[T]
       twice2(square)(3)
              ^

scala> def twice3[T](a: T, f: T => T) = f(f(a))
twice3: [T](a: T,f: (T) => T)T

scala> twice3(3, square)
<console>:8: error: could not find implicit value for evidence parameter of type
 Numeric[T]
       twice3(3, square)

scala> def twice[T](a: T)(f: T => T) = f(f(a))
twice: [T](a: T)(f: (T) => T)T

scala> twice(3)(square)
res0: Int = 81

So evidently the type of "twice(3)" needs to be known before the implicit can be resolved. I guess that makes sense, but I'd still be glad if a Scala guru could comment on this one...

Andrew Phillips
  • 1,050
  • 9
  • 16
1

Another solution is to lift square into partially applied function:

scala> twice(square(_:Int),2)
res1: Int = 16

This way the implicit is applied to square as in:

scala> twice(square(_:Int)(implicitly[Numeric[Int]]),2)
res3: Int = 16

There is even another approach:

def twice[T:Numeric](f: (T) => T, a: T): T = f(f(a))
scala> twice[Int](square,2)
res1: Int = 16

But again, the type parameter don't get inferred.

pedrofurla
  • 12,763
  • 1
  • 38
  • 49
  • This is probably the better approach. But what I don't like is that I know explicitly need to specify a type, where it seems to be possible to infer the type. – Wilfred Springer Jan 04 '11 at 09:33
1

Your problem is that square isn't a function (ie. a scala.Function1[T, T] aka (T) => T). Instead it's a type parametrized method with multiple argument lists one of which is implicit ... there's no syntax in Scala to define an exactly equivalent function.

Interestingly, your use of the Numeric type class means that the usual encodings of higher-ranked functions in Scala don't directly apply here, but we can adapt them to this case and get something like this,

trait HigherRankedNumericFunction {
  def apply[T : Numeric](t : T) : T
}

val square = new HigherRankedNumericFunction {
  def apply[T : Numeric](t : T) : T = implicitly[Numeric[T]].times(t, t)
}

This gives us a higher-ranked "function" with its type parameter context-bounded to Numeric,

scala> square(2)
res0: Int = 4

scala> square(2.0)
res1: Double = 4.0

scala> square("foo")
<console>:8: error: could not find implicit value for evidence parameter of type Numeric[java.lang.String]
   square("foo")

We can now define twice in terms of HigherRankedNumericFunctions,

def twice[T : Numeric](f : HigherRankedNumericFunction, a : T) : T = f(f(a))

scala> twice(square, 2)
res2: Int = 16

scala> twice(square, 2.0)
res3: Double = 16.0

The obvious downside of this approach is that you lose out on the conciseness of Scala's monomorphic function literals.

Miles Sabin
  • 23,015
  • 6
  • 61
  • 95
  • Ok, I got it, but I'm not satisfied yet. I don't want twice to be defined for numeric times only. – Wilfred Springer Jan 04 '11 at 11:11
  • Miles, we could take this further and define a `trait HigherKindedImplicitFunction[Kind[_]] { def apply[T: Kind](t: T): T }`, possibly generalizing this over many more parameters (where types that don't need an implicit could get an `Id[_]` instead), right? This then wouldn't require `twice` to require an instance of `Numeric[T]`, but instead of a `Kind[T]` which is, with `square`, `Numeric`. – Adowrath Nov 02 '17 at 13:03