What is the difference between parametric polymorphism with upper-bound and subtype polymorphism in these methods?

Viewed 115

Suppose we have two method signatures

def drive[U <: Vehicle](c : U) = ???
def drive(c : Vehicle) = ???

What are the differences between these definitions? Is the second one short-hand for the first or are they fundamentally different? Are they conceptually different or just that the scala language treats them differently?

1 Answers

Consider terminology used by Miles to conceptually distinguish between the two kinds of polymorphism

  • parametric polymorphism,
  • subtype polymorphism

The method

def drive(c: Vehicle)

is both parametrically monomorphic, as it has no type parameters, and subtype polymorphic as it can take a Lada as well as Tesla.

Now in this particular case the two methods are equally constrained

def drive[U <: Vehicle](c : U) = ???
def drive(c : Vehicle) = ???

however slight modification reveals some differences

scala> sealed trait Vehicle
     | case object Škoda extends Vehicle
     | case object Tesla extends Vehicle
trait Vehicle
object Škoda
object Tesla

scala> def f(a: Vehicle, b: Vehicle) = a
def f(a: Vehicle, b: Vehicle): Vehicle

scala> f(Škoda, Tesla)
val res21: Vehicle = Škoda

scala> def f[A <: Vehicle](a: A, b: A) = a
def f[A <: Vehicle](a: A, b: A): A

scala> f(Škoda, Tesla)
val res22: Product with Vehicle with java.io.Serializable = Škoda

Note how with parametric polymorphism we activated inference and unification so compiler deduced a more precise type for the vehicle arguments.

In general, parametrically polymorphic methods allow us to ask more of the compiler and put further compile-time constraints on its type parameters via type classes

trait FixableByHittingIt[A]

implicit val fixLadaByHittingIt = new FixableByHittingIt[Lada.type] {}

def drive[U <: Vehicle](c : U)(implicit ev: FixableByHittingIt[U]) = ???

drive(Lada)   // ok
drive(Tesla)  // nope, needs sci-fi mechanic

As a personal side note, IMO type parameters are a gateway to the rich and fun world of Scala type-checker. Once you start perceiving the type-checker as just another kind of machine that can be programmed like many other machines you are daily successful at programming, cryptic expressions at type-level become less intimidating, as you realise you already posess all the mental tools need to program, they just need to be applied to a different machine which happens to run at a different time in program's life cycle. So the interesting difference between your two methods is that the parametrically polymorphic one is a gateway drug :)

Related