Nick says:
In this function
head
, it takes a list ofA
’s, and returns anA
. And it doesn’t matter what theA
is: It could beInt
s,String
s,Oranage
s,Car
s, whatever. AnyA
would work, and the function is defined for everyA
that there can be.
scala> def head[A](xs: List[A]): A = xs(0)
head: [A](xs: List[A])A
scala> head(1 :: 2 :: Nil)
res0: Int = 1
scala> case class Car(make: String)
defined class Car
scala> head(Car("Civic") :: Car("CR-V") :: Nil)
res1: Car = Car(Civic)
Haskell wiki says:
Parametric polymorphism refers to when the type of a value contains one or more (unconstrained) type variables, so that the value may adopt any type that results from substituting those variables with concrete types.
Let’s think of a function plus
that can add two values of type A
:
scala> def plus[A](a1: A, a2: A): A = ???
plus: [A](a1: A, a2: A)A
Depending on the type A
, we need to provide different definition for what it means to add them. One way to achieve this is through subtyping.
scala> trait Plus[A] {
def plus(a2: A): A
}
defined trait Plus
scala> def plus[A <: Plus[A]](a1: A, a2: A): A = a1.plus(a2)
plus: [A <: Plus[A]](a1: A, a2: A)A
We can at least provide different definitions of plus
for A
. But, this is not flexible since trait Plus
needs to be mixed in at the time of defining the datatype. So it can’t work for Int
and String
.
The third approach in Scala is to provide an implicit conversion or implicit parameters for the trait.
scala> trait Plus[A] {
def plus(a1: A, a2: A): A
}
defined trait Plus
scala> def plus[A: Plus](a1: A, a2: A): A = implicitly[Plus[A]].plus(a1, a2)
plus: [A](a1: A, a2: A)(implicit evidence$1: Plus[A])A
This is truely ad-hoc in the sense that
A
Int
) without access to its source code
The last point makes Scala’s ad-hoc polymorphism more powerful than that of Haskell. More on this topic can be found at Debasish Ghosh @debasishg’s Scala Implicits : Type Classes Here I Come.
Let’s look into plus
function in more detail.