Implicits Confusion

255 Views Asked by At

The following polymorphic methods compile:

import spire.math._
import spire.implicits._

scala> def foo[A:Numeric](d : A) = 2 * d
foo: [A](d: A)(implicit evidence$1: spire.math.Numeric[A])A

scala> def foo[A:Numeric](d : A) = 2 + d
foo: [A](d: A)(implicit evidence$1: spire.math.Numeric[A])A

However, if I change the integer 2 to the double 2.0 the compiler complains about not finding an implicit value

scala> def foo[A:Numeric](d : A) = 2.0 + d
<console>:19: error: could not find implicit value for parameter ev: spire.algebra.Field[A]
       def foo[A:Numeric](d : A) = 2.0 + d
                                       ^

scala> def foo[A:Numeric](d : A) = 2.0 * d
<console>:19: error: could not find implicit value for parameter ev: spire.algebra.Field[A]
       def foo[A:Numeric](d : A) = 2.0 * d
                                       ^

I have tried to understand some of the other questions and answers on SO but I am none the wiser on how to solve this.

2

There are 2 best solutions below

1
On

I find the clearest way to see what's going on with implicits is to use reify (your IDE may provide similar functionality):

scala> import scala.reflect.runtime.universe.reify
import scala.reflect.runtime.universe.reify

scala> reify { def foo[A:Numeric](d : A) = 2 * d }
res1: reflect.runtime.universe.Expr[Unit] =
Expr[Unit]({
  def foo[A](d: A)(implicit evidence$1: Numeric[A]) = implicits.literalIntMultiplicativeSemigroupOps(2).$times(d)(evidence$1);
  ()
})

And looking in the spire source we see:

final class LiteralIntMultiplicativeSemigroupOps(val lhs: Int) extends AnyVal {
  def *[A](rhs:A)(implicit ev: Ring[A]): A = ev.times(ev.fromInt(lhs), rhs)
}

This is an implicit class that makes a * method available on Ints, provided the value on the right (d in your code) is some A which forms a Ring (which any Numeric A will). So your first example works. But in the second example there's no such implicit (no LiteralDoubleMultiplicativeSemigroupOps or the like), so the second example doesn't compile.

0
On

To expand on the answer from lmm:

to add or multiply something of type T to an integer, spire requires just the spire.algebra.Ring[T] typeclass, which spire.algebra.Numeric[T] extends.

See for example LiteralIntAdditiveSemigroupOps in spire.syntax.Ops

to add or multiply something of type T to a double, spire requires an instance of the spire.algebra.Field[T] typeclass, which spire.algebra.Numeric[T] does not extend.

See for example LiteralDoubleAdditiveSemigroupOps in spire.syntax.Ops

The reason for this design decision:

The result of e.g. adding an Int to a T has to be T. So you need a way to convert an Int to T. This is available for spire.algebra.Ring[T] (the fromInt method). To convert an integer n to T, just use the one element of T and sum it n times using the + operation of T.

The result of e.g. adding a Double to a T has also to be T. But for that you need a way to convert a Double to a T, which is much more complex and is only available in spire.algebra.Field[T] (the fromDouble method). Take a look at the generic fromDouble method in Field. It is some pretty complex code that makes use of the div operation of Field[T], which of course does not exist on Ring[T].

How to modify your code

If you really want to multiply with a double for whatever reason, you have to modify the method like this:

def foo[A: Field](d: A) = 2.0 * d