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?
Generic functions are functions declared with one or more generic type parameters. They may be methods in a class or struct , or standalone functions. A single generic declaration implicitly declares a family of functions that differ only in the substitution of a different actual type for the generic type parameter.
To use a generic class, put the type in the square brackets in place of A . Class Apple and Banana both extend Fruit so we can push instances apple and banana onto the stack of Fruit . Note: subtyping of generic types is *invariant*.
In Scala, forming a Generic Class is extremely analogous to the forming of generic classes in Java. The classes that takes a type just like a parameter are known to be Generic Classes in Scala. This classes takes a type like a parameter inside the square brackets i.e, [ ].
Most Scala generic classes are collections, such as the immutable List, Queue, Set, Map, or their mutable equivalents, and Stack. Collections are containers of zero or more objects. We also have generic containers that aren't so obvious at first.
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
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With