How to be
polymorphic in Scala

by  Yuriy Polyulya  /  @polyulya  /  +blog

Polymorhism

is a programming language feature that allows one interface to be used for a general class of actions.


url

John C. Reynolds. Theories of programming languages. Cambridge University Press, New York, NY, USA, 1999.

Parametric

Parametric (type as parameter)

defined on functions or a data types so that they can be written generically and it can handle values identically without depending on their type.



class Container[A](val value : A) {              // Type-constructor
  def map[B](f : A => B): Container[B] =
    new Container(f(value))
}

new Container(10) map { x => (x * 2).toString } // Container(“20”)
new Container("10") map { x => x.toInt * 2 }    // Container(20)
        

Higher-kinded (ranked) polymorphism

Kind is the type of a type-constructor (type of of a higher-order type operator).


  • Value level:

    
    val x : String
    val y : List[String]
    
    def id(x : Int):Int = x
    def map(f : Int=> Int, x: Int) = f(x)
                
  • Type level:

    
    type String     :: *
    type List       :: * -> *
    type Function1  :: * -> * -> *
    
    type Id[A] = A
    type Map[C[_],A] = C[A]
                

Higher-kinded (ranked) polymorphism


    • Kind systems classify types
    • Values are to types as types are to kinds
    • “Higher” kinds are the kinds of type constructors


  • Predicative polymorphism:

    
    trait ~>[T[_],R[_]] {  // T & R - have predicates [_]
      def apply[A](a : T[A]): R[A]
    }
    
    val naturalTransformation = new (List ~> Option) { //  ~>[List, Option]
      def apply[A](a : List[A]): Option[A] = a.headOption
    }
                

Declaration-Site Variance

way of propagation types relation rules to type constructor relation rules.


Meaning Scala notation
Covariant C[T’] is a subclass of C[T] [+T]
Contravariant C[T] is a subclass of C[T’] [-T]
Invariant C[T] and C[T’] are not related [T]

Declaration-Site Variance

way of propagation types relation rules to type constructor relation rules.


  • url
  • url
  • url
  • url

Using-Site Variance

by using upper and lower bounds notations.


  • For types:

    
    case class Employee(name: String)
    case class Developer(yearsOfExperince: Int)
    // and container:
    class Foo[T]
                
  • Using-Site Covariance:

    
    def apply(a : Foo[_ <: Employee]): String
    // or by existential type declaration:
    def apply(a: Foo[X] forSome { type X <: Employee}): String
                
  • Using-Site Contravariance:

    
    def apply[A](a: Foo[_ >: Employee]): String
    // or by existential type declaration:
    def apply(a : Foo[X] forSome { type X >: Employee}): String
                

Inclusion

Inclusion (Inheritance and Subtyping)

is a form of type polymorphism in which a subtype is a data-type that is related to another data-type (the super-type) by some notion of substitutability.


  • Subtype polymorphism:

    
    class Animal
    trait Furry extends Animal
    trait HasLegs extends Animal
    trait FourLegged extends Animal with HasLegs
    class Cat extends Animal with Furry with FourLegged
    
    val cat = new Cat()
                

  • Constructor calls:

Traits in Scala

1. “Rich Interface” role:


trait RichIterable[A] {
  def iterator : java.util.Iterator[A]  // contract method

  def foreach(f : A => Unit) = {
    val iter = iterator
    while (iter.hasNext) f(iter.next)
  }

  def foldLeft[B](seed : B)(f : (B, A) => B) = {
    var result = seed
    foreach(e => result = f(result, e))
    result
  }
}

val richSet =
  new java.util.HashSet[Int]
  with RichIterable[Int]

richSet.add(1)
richSet.add(2)
richSet.foldLeft(0)((x, y) => x + y)  // == 3
          

2. “Stackable modification” role:


trait IgnoreCaseSet extends java.util.Set[String] {
  abstract override def add(e : String) = {
    super.add(e.toLowerCase)
  }

  abstract override def contains(e: Object) = {
    e match {
      case s: String  => super.contains(s.toLowerCase)
      case o          => super.contains(o)
    }
  }
}

val set =
  new java.util.HashSet[String]
  with IgnoreCaseSet

set.add("HI THERE")       // uppercase
set.contains("hi there")  // lowercase
          

3. “Multiple View” role:


//Facets
trait Entity { ... }
trait InventoryItemSet { ... }
trait PurchaseLimiter { ... }
trait MailNotifier { ... }
trait Versioned { ... }
trait Transactional { ... }

// Composition
val order = new Order(customer)
   with Entity
   with InventoryItemSet
   with PurchaseLimiter
   with MailNotifier
   with Versioned
   with Transactional
          

Overloading

Overloading

allows creating several methods with the same name which differ from each other in the type of the input and the output of the function.


Operators overload:


case class Complex(re : Double, im : Double) {
  def + (another : Complex) =
    new Complex(re + another.re, im + another.im)

  def unary_- =
    new Complex(-re, -im)
}

Complex(2, 5) + Complex(1, -2)  // == Complex(3, 3)
-Complex(1, -2)                 // == Complex(-1, 2)
        

Coercion

Coercion

is the operation of converting an argument or an operand to the type expected by a function or an operator.


Coercion by implicit conversions:


case class Complex(re : Double, im : Double) {
  def + (another : Complex) =
    new Complex(re + another.re, im + another.im)
}

//implicit conversion: a form of coercion
implicit def doubleToComplex(d: Double) = Complex(d, 0)

Complex(2.0, 5.0) + 5.0   // == Complex(7.0, 5.0)
5.0 + Complex(1.0, -2.0)  // == Complex(6.0, -2.0)
        

Implicits in Scala

1.1. Implicit Conversion:

  • 
    val str: String = "test - ok"
    
    //C/C++ style condition expression:
    if(str) println(str)  //error: type mismatch
                  

  • AnyRef to Boolean coversion needed

  • 
    implicit def anyRefIsNotNull(x: AnyRef): Boolean = x != null
    
    //C/C++ style condition expression:
    if(str) println(str)  //out: test - ok
                  

1.2. Syntax extension by implicit conversions:

  • Ternary operator similar to ? in C/C++:

    
    val res : String = (4*4 > 14) ? "GT" | "LT or EQ"
                  
  • Implicit coversion from Boolean to something with ? method:

    
    implicit def boolToOperator(c : Boolean) = new {
      def ?[A](t : => A) = new {
        def |(f : => A) = if(c) t else f
      }
    }
                  
  • Or:

    
    class MakeIfTrue(b : => Boolean) {
      def ?[A](t : => A) = new IfTrue[A](b,t)
    }
    
    class IfTrue[A](b : => Boolean, t : => A) {
      def |(f : => A) = if (b) t else f
    }
    
    implicit def autoMakeIfTrue(b: => Boolean) = new MakeIfTrue(b)
                  

2. Implicit parameters:


def multiplier(i : Int)(implicit factor : Int) {
  println(i * factor)
}

implicit val factor = 2

val res1 = multiplier(2)      // res: 4
val res2 = multiplier(2)(3)   // res: 6
          

Type-Classes

Type-Classes

is a type system construct that supports ad hoc polymorphism. This is achieved by adding constraints to type variables in parametrically polymorphic types.


  • In Haskell:

    
    class Show a where
      show :: a -> String
                

  • In Scala:

    
    trait Show[A] {
      def shows(a: A): String
    }
    
    //OR Haskell notation:
    trait Show[A] {
      def shows : A => String
    }            

Type-Classes

is a type system construct that supports ad hoc polymorphism. This is achieved by adding constraints to type variables in parametrically polymorphic types.


Type-classes implementation:


// Type-class Int instance
implicit val IntShow = new Show[Int] {
  def shows(a : Int) = a.toString
}

//Type-class List instance:
implicit def ListShow[T] = new Show[List[T]] {
  def shows(a : List[T]) = a.mkString(", ")
}
        

Using a Type-classes


def shows[A](a : A)(implicit sh: Show[A]) = sh.shows(a)
//OR:
def shows[A : Show](a : A) = implicitly[Show[A]].shows(a)

// must have a "Show[Int]" instance in scope
shows(42)
        

Type-Classes

is a type system construct that supports ad hoc polymorphism. This is achieved by adding constraints to type variables in parametrically polymorphic types.


Pimps Type-classes (syntax extension)


trait ShowSyntax[A] {
  def shows : String
}

implicit def toShowSyntax[A : Show](a : A)= new ShowSyntax[A] {
  def shows = implicitly[Show[A]].shows(a)
}
        

Using a syntax extension


// must have a "Show[Int]" instance in scope
42.shows
        

What have we gained?

  • We can declare instances outside of the types themselves
    • Int knows nothing about Show.
  • This is the open world assumption
  • In Scala, we can override the typeclass instance by putting a new one in scope:
    
    println(5.shows)    // prints '5‘
    
    localy {
       implicit val AltIntShow = new Show[Int] {
          def shows(i : Int) = (1 to i) map(_ => "|") mkString
       }
    
       println(5.shows)   // prints '|||||'
    }
                
  • Questions?


  • Remarks?