53

I am trying to learn Scala now, with a little bit of experience in Haskell. One thing that stood out as odd to me is that all function parameters in Scala must be annotated with a type - something that Haskell does not require. Why is this? To try to put it as a more concrete example: an add function is written like this:

def add(x:Double, y:Double) = x + y

But, this only works for doubles(well, ints work too because of the implicit type conversion). But what if you want to define your own type that defines its own + operator. How would you write an add function which works for any type that defines a + operator?

Dan Burton
  • 53,238
  • 27
  • 117
  • 198
airportyh
  • 21,948
  • 13
  • 58
  • 72

5 Answers5

70

Haskell uses Hindley-Milner type inference algorithm whereas Scala, in order to support Object Oriented side of things, had to forgo using it for now.

In order to write an add function for all applicable types easily, you will need to use Scala 2.8.0:

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

scala> import Numeric._
import Numeric._

scala> def add[A](x: A, y: A)(implicit numeric: Numeric[A]): A = 
     | numeric.plus(x, y)
add: [A](x: A,y: A)(implicit numeric: Numeric[A])A

scala> add(1, 2)
res0: Int = 3

scala> add(1.1, 2.2)
res1: Double = 3.3000000000000003
Walter Chang
  • 11,547
  • 2
  • 47
  • 36
  • That's impressive. Does the implicit Numeric constrain A to be a subtype of Numeric? Or is the target type only required to supply a "plus" method? –  Aug 10 '09 at 13:47
  • 2
    Thanks! This is like type classes in Haskell, essentially. I also found a good explanation of *implicit* at http://www.scala-lang.org/node/114. – airportyh Aug 10 '09 at 17:54
  • 6
    @mtnygard No, "A" does not have any subtyping constraints. The magic happens in the imported scala.Numeric object. There are a number of implicit objects that all implement Numeric trait and scalac will pick the appropriate one when it compiles. Magical stuff, really. please check the api docs for detail. – Walter Chang Aug 11 '09 at 03:02
  • I had a problem passing add to function subsequently. (Like, I defined a function twice[T](f: (T) => T, T n) = f(f(n)), and tried to invoke it like this: twice(add, 2), but it failed: could not find implicit value for parameter numeric: Numeric[T]. Anyone? – Wilfred Springer Jan 04 '11 at 08:28
  • 2
    Equivalently, you can write the _same_ code with context bounds: def add[A: Numeric](x: A, y: A) = implicitly[Numeric[A]].plus(x, y) In many cases you don't even need to use implicitly to recover the implicitly passed parameter, because it will be implicitly passed to other functions expecting it. E.g.: scala> def addTwice[A: Numeric](x: A, y: A) = add(add(x, y), y) addTwice: [A](x: A,y: A)(implicit evidence$1: Numeric[A])A scala> addTwice(1, 2) res2: Int = 5 – Blaisorblade Mar 27 '11 at 00:03
19

In order to solidify the concept of using implicit for myself, I wrote an example that does not require scala 2.8, but uses the same concept. I thought it might be helpful for some. First, you define an generic-abstract class Addable:

scala> abstract class Addable[T]{
 |   def +(x: T, y: T): T
 | }
defined class Addable

Now you can write the add function like this:

scala> def add[T](x: T, y: T)(implicit addy: Addable[T]): T = 
 | addy.+(x, y)
add: [T](T,T)(implicit Addable[T])T

This is used like a type class in Haskell. Then to realize this generic class for a specific type, you would write(examples here for Int, Double and String):

scala> implicit object IntAddable extends Addable[Int]{
 |   def +(x: Int, y: Int): Int = x + y
 | }
defined module IntAddable

scala> implicit object DoubleAddable extends Addable[Double]{
 |   def +(x: Double, y: Double): Double = x + y
 | }
defined module DoubleAddable

scala> implicit object StringAddable extends Addable[String]{
 |   def +(x: String, y: String): String = x concat y
 | }
defined module StringAddable

At this point you can call the add function with all three types:

scala> add(1,2)
res0: Int = 3

scala> add(1.0, 2.0)
res1: Double = 3.0

scala> add("abc", "def")
res2: java.lang.String = abcdef

Certainly not as nice as Haskell which will essentially do all of this for you. But, that's where the trade-off lies.

airportyh
  • 21,948
  • 13
  • 58
  • 72
3

I think the reason Scala requires the type annotation on the parameters of a newly defined function comes from the fact that Scala uses a more local type inference analysis than that used in Haskell.

If all your classes mixed in a trait, say Addable[T], that declared the + operator, you could write your generic add function as:

def add[T <: Addable[T]](x : T, y : T) = x + y

This restricts the add function to types T that implement the Addable trait.

Unfortunately, there is not such trait in the current Scala libraries. But you can see how it would be done by looking at a similar case, the Ordered[T] trait. This trait declares comparison operators and is mixed in by the RichInt, RichFloat, etc. classes. Then you can write a sort function that can take, for example, a List[T] where [T <: Ordered[T]] to sort a list of elements that mix in the ordered trait. Because of implicit type conversions like Float to RichFloat, you can even use your sort function on lists of Int, or Float or Double.

As I said, unfortunately, there is no corresponding trait for the + operator. So, you would have to write out everything yourself. You would do the Addable[T] trait, create AddableInt, AddableFloat, etc., classes that extend Int, Float, etc. and mix in the Addable trait, and finally add implicit conversion functions to turn, for example, and Int into an AddableInt, so that the compiler can instantiate and use your add function with it.

Ram Ghadiyaram
  • 28,239
  • 13
  • 95
  • 121
fxt
  • 81
  • 3
3

Haskell uses the Hindley-Milner type inference. This kind of type-inference is powerful, but limits the type system of the language. Supposedly, for instance, subclassing doesn't work well with H-M.

At any rate, Scala type system is too powerful for H-M, so a more limited kind of type inference must be used.

Community
  • 1
  • 1
Daniel C. Sobral
  • 295,120
  • 86
  • 501
  • 681
1

The function itself will be pretty straightforward:

def add(x: T, y: T): T = ...

Better yet, you can just overload the + method:

def +(x: T, y: T): T = ...

There's a missing piece, though, which is the type parameter itself. As written, the method is missing its class. The most likely case is that you're calling the + method on an instance of T, passing it another instance of T. I did this recently, defining a trait that said, "an additive group consists of an add operation plus the means to invert an element"

trait GroupAdditive[G] extends Structure[G] {
  def +(that: G): G
  def unary_- : G
}

Then, later, I define a Real class that knows how to add instances of itself (Field extends GroupAdditive):

class Real private (s: LargeInteger, err: LargeInteger, exp: Int) extends Number[Real] with Field[Real] with Ordered[Real] {
  ...

  def +(that: Real): Real = { ... }

  ...
}

That may be more than you really wanted to know right now, but it does show both how to define generic arguments and how to realize them.

Ultimately, the specific types aren't required, but the compiler does need to know at least the type bounds.

  • This method seems to require *add* to be a method in a class though. – airportyh Aug 10 '09 at 17:56
  • Well, yes. Don't all methods have to be in a class? Even in the original example, defining "add" in the REPL created it inside an implicitly-defined class. –  Aug 11 '09 at 18:40
  • Your solution requires wrapping numbers in the class Real; in Scala that's a bad idea, because you can encode Haskell typeclasses using implicits, with better syntax. – Blaisorblade Mar 26 '11 at 23:55