user3248346
user3248346

Reputation:

Treat Type Parameter As A Numeric Using Implicits

I'm having trouble with the following polymorphic function definition:

scala> def foo[A](x : A, y : A)(implicit o : A => Numeric[A]) : A = x + y
<console>:7: error: type mismatch;
 found   : A
 required: String
       def foo[A](x : A, y : A)(implicit o : A => Numeric[A]) : A = x + y

I would like to specify that the type parameter A can be used as a Numeric but it's not working. What am I doing wrong?

Upvotes: 2

Views: 101

Answers (2)

Didier Dupont
Didier Dupont

Reputation: 29528

A Numeric[A] is typically a single instance, that gives access to arithmetic operations on the type A. It will contains def plus(x: A, y: A): A, rather than def add(x: A): A, which would have some this of type A.

It is akin to java's Comparator (or scala's Ordering) which has a Compare(T o1, T o2), rather than to java Comparable (scala's Ordered) which has a CompareTo(T other). Such types, with implicitly available singleton implementations, are typically called typeclasses, after a closely related feature in the Haskell language.

So you don't want to convert A to Numeric[A], you just want a Numeric[A]. Your signature should be

def foo[A](x: A, y: A)(implicit numeric: Numeric[A])

There is a shortcut for that, which is

def foo[A: Numeric](x: A, y: A)

In method foo, you can do numeric.plus(x,y), but if you import scala.math.Numeric._, you get some magic and the much more convenient x + y.


Regarding the question in comment : has there been a language change since Programming in scala (which is on v 2.8) ?

The syntax [A: Numeric], called a context bound, has been introduced late in the language, I'm not 100% sure, but I believe it was not available in 2.8. But this is just a shortcut for an implicit Numeric[A] argument, which was possible in 2.8 (Numeric was available -just introduced actually- in 2.8), and should have been used as shown above at the time. Except for that, there was no language change.

However, in the early times of scala, it was more common to use "views", meaning implicit conversions, rather than typeclasses, that is to use types such as Comparable/Ordered, rather than Comparator/Ordering/Numeric.

For such types, you would typically use an upper bound A <: Ordered[A]. However, this may be a bit too restrictive as it will works only if the implementor had the foresight to implement Ordered. You can get around that but requiring that A may be converted to an Ordered[A], so implicit A => Ordered[A].

There used to be a shortcut for that, [A <% Ordered[A]], called a view bound. However, it has been deprecated, and while views are still possible (implicit A => Ordered[A]), typeclasses are much preferred. There are a lot of reasons for that, e.g.:

  • having conversion functions in the implicit scope will act as implicit casts and are quite dangerous
  • typeclasses are more powerful. For instance, you can have an instance of Numeric[A] even when you have no instances of A around, and so get the zero of type A. if you call sum on a list and the list is empty, the Numeric is still available and you can get the zero. if Numeric was something implemented by A, even if it made zero available, you would have no instance to call it on. Another example, a typeclass may be used as a factory, again when you have no instance of the constructed type (yet), as demonstrated in this answer.

To quote Martin Odersky (discussing deprecation):

context bounds are essentially the replacement of view bounds. It's what we should have done from the start, but we did not know better back then

Upvotes: 4

Arne Claassen
Arne Claassen

Reputation: 14404

The two changes you need is that, first the implicit should not be a function from A to Numeric[A], but simply Numeric[A], and second you need to import Numeric.Implicits to get the implicit Ops that enable +.

import scala.math.Numeric.Implicits._

def foo[A](x : A, y : A)(implicit num: Numeric[A]) : A = x + y

Without the import you'd have to write it as:

def foo[A](x : A, y : A)(implicit num: Numeric[A]) : A = num.plus(x,y)

Upvotes: 2

Related Questions