Typeclasses in a galaxy far, far away

Michał Płachta

This talk won't be about

  • Scala type system
  • Types of polimorphisms

What is it about?

  • recognizing typeclasses

  • writing simple typeclasses

  • knowing about more complex ones

Quick Quiz

What is this function?

class What[+A, +Repr] {
  def what[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That
}

Quick Quiz

This is map

trait TraversableLike[+A, +Repr] {
  def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That
}

Agenda

  • Creating our own typeclass
  • Using standard Scala typeclasses
  • Typeclasses around the world

Case: Yodish Translator

  • Let's write a generic method
  • It should get an object and return its translation to Yodish
  • it should do it for many types
  • E.g.
Strings: "Vader is strong" => "strong is Vader"
Ints: 321 => 123

Random approach

  // classic approach - no typeclass yet!
  trait Yodaizable[A] {
    def yodaize : A
  }

  case class YodaizableString(x : String) extends Yodaizable[String] {
    def yodaize = x.split(" ").reverse.mkString(" ")
  }

  println(YodaizableString("Vader is strong").yodaize)

Step 1: Typeclass definition

trait Yodaizator[A] {
 def yodaize(a : A) : A
}

Notice a subtle difference to the previous one

trait Yodaizable[A] {
  def yodaize : A
}

Step 2: Usage definition

def yodaize[A](a : A)(implicit yodaizator : Yodaizator[A]) = 
  yodaizator.yodaize(a)
  • we have a generic function
  • with per-type implementation
  • compiler is responsible for finding the right implementation

Is it enough?

> println(yodaize("Vader is strong")) 
Error:(29, 18) could not find implicit value for 
evidence parameter of type Yodaizator[String]

Step 3: Creating witness (String)

implicit val stringYodaizator = new Yodaizator[String] {
 override def yodaize(a: String): String = 
   a.split(" ").reverse.mkString(" ") // cheating!
}
> println(yodaize("Vader is strong")) 
strong is Vader

Step 3: Creating witness (Int)

  implicit val intYodaizator = new Yodaizator[Int] {
    override def yodaize(a: Int): Int = 
      a.toString().reverse.toInt // cheating :(
  }
> println(yodaize(321)) 
123

Step by step

// 1. Typeclass definition
@implicitNotFound("Yodaizator instance not found for type ${A}")
trait Yodaizator[A] {
 def yodaize(a : A) : A
}

// 2. Typeclass usage definition
def yodaize[A](a : A)(implicit yodaizator : Yodaizator[A]) = yodaizator.yodaize(a)

// 3. Adding classes to the typeclass (creating witness objects)
implicit val stringYodaizator = new Yodaizator[String] {
 override def yodaize(a: String): String = a.split(" ").reverse.mkString(" ")
}

implicit val intYodaizator = new Yodaizator[Int] {
 override def yodaize(a: Int): Int = a.toString().reverse.toInt
}

// 4. Use function with an object of any class that belongs to the typeclass
println(yodaize("Vader is strong")) // => "strong is Vader"
println(yodaize(321)) // => 123

Belongs to typeclass

def yodaize[A](a : A)(implicit yodaizator : Yodaizator[A]) = 
    yodaizator.yodaize(a)
def yodaize[A : Yodaizator](a : A) = implicitly[Yodaizator[A]].yodaize(a)

Typeclass pattern

  • generic interfaces...
  • that provide a common feature set...
  • over a wide variety of types

Random typeclasses

Ordering[T]

  • generic interface...
  • that provide ordering
  • over a wide variety of types.

Ordering[T]

scala> case class Planet(name: String, diameter: Int, population: Int)
defined class Planet

scala>

Ordering[T]

scala> case class Planet(name: String, diameter: Int, population: Int)
defined class Planet

scala> val planets : List[Planet] = ...

scala> planets.sorted
<console>:11: error: No implicit Ordering defined for Planet.
              planets.sorted
                      ^

scala>

Ordering[T]

scala> case class Planet(name: String, diameter: Int, population: Int)
defined class Planet

scala> val planets : List[Planet] = ...

scala> planets.sorted
<console>:11: error: No implicit Ordering defined for Planet.
              planets.sorted
                      ^

scala> implicit val planetOrdering = new Ordering[Planet] {
     | override def compare(x: Planet, y: Planet): Int =
     | y.population - x.population
     | }
planetOrdering: Ordering[Planet] = $anon$1@739ca8fe

scala> planets.sorted
res3: List[Planet] = List(
  Planet(Naboo,12120,4500000), Planet(Alderaan,12500,2000000), 
  Planet(Bespin,118000,6000), Planet(Tatooine,10465,200))

scala>

Ordering companion object

  • CharOrdering
  • IntOrdering
  • BooleanOrdering
  • ...

Random typeclasses

CanBuildFrom[-From, -Elem, +To]

  • generic interface...
  • that provide collection factories
  • over a wide variety of types.

CanBuildFrom[-From, -Elem, +To]

scala> import scala.collection.generic.CanBuildFrom
scala> import scala.collection.immutable.BitSet

scala> val bs = BitSet(1,2,3)
bs: scala.collection.immutable.BitSet = BitSet(1, 2, 3)

scala>

CanBuildFrom[-From, -Elem, +To]

scala> import scala.collection.generic.CanBuildFrom
scala> import scala.collection.immutable.BitSet

scala> val bs = BitSet(1,2,3)
bs: scala.collection.immutable.BitSet = BitSet(1, 2, 3)

scala> val shifted = bs map (_+1)
shifted: scala.collection.immutable.BitSet = BitSet(2, 3, 4)

CanBuildFrom[-From, -Elem, +To]

scala> import scala.collection.generic.CanBuildFrom
scala> import scala.collection.immutable.BitSet

scala> val bs = BitSet(1,2,3)
bs: scala.collection.immutable.BitSet = BitSet(1, 2, 3)

scala> val shifted = bs map (_+1)
shifted: scala.collection.immutable.BitSet = BitSet(2, 3, 4)

scala> val stringified = shifted map (_+"!") // What's the result?

CanBuildFrom[-From, -Elem, +To]

scala> import scala.collection.generic.CanBuildFrom
scala> import scala.collection.immutable.BitSet

scala> val bs = BitSet(1,2,3)
bs: scala.collection.immutable.BitSet = BitSet(1, 2, 3)

scala> val shifted = bs map (_+1)
shifted: scala.collection.immutable.BitSet = BitSet(2, 3, 4)

scala> val stringified = shifted map (_+"!")
stringified: scala.collection.immutable.SortedSet[String] = 
  TreeSet(2!, 3!, 4!)

CanBuildFrom[-From, -Elem, +To]

scala> import scala.collection.generic.CanBuildFrom
scala> import scala.collection.immutable.BitSet

scala> val bs = BitSet(1,2,3)
bs: scala.collection.immutable.BitSet = BitSet(1, 2, 3)

scala> val shifted = bs map (_+1)
shifted: scala.collection.immutable.BitSet = BitSet(2, 3, 4)

scala> val stringified = shifted map (_+"!")
stringified: s.c.i.SortedSet[String] = TreeSet(2!, 3!, 4!)

scala> val cbf = implicitly[CanBuildFrom[BitSet, String, Set[String]]]
cbf: CanBuildFrom[BitSet,String,Set[String]] = 
     scala.collection.generic.SortedSetFactory$SortedSetCanBuildFrom

Random typeclasses

Functor[F[_]]

  • generic interface...
  • that provides mapping
  • over a wide variety of types.

Functor[F[_]]

scala> import java.util._
import java.util._

scala> val arrayList = new ArrayList(Arrays.asList(1, 2, 3))
arrayList: java.util.ArrayList[Int] = [1, 2, 3]

scala> arrayList.map(_ + 1)
<console>:14: error: value map is not a member of j.u.ArrayList[Int]
              arrayList.map(_ + 1)

Functor[F[_]]

  // 1. typeclass definition
  trait Functor[F[_]] {
    def map[X, Y](f: X => Y): F[X] => F[Y]
  }

Functor[F[_]]

  // 1. typeclass definition
  trait Functor[F[_]] {
    def map[X, Y](f: X => Y): F[X] => F[Y]
  }

  // 2. typeclass usage definition
  implicit def somethingToFunctor[F[_]: Functor, A](fa: F[A]) = new {
    val witness = implicitly[Functor[F]]
    final def map[B](f:A => B) : F[B] = witness.map(f)(fa)
  }

Functor[F[_]]

  // 1. typeclass definition
  trait Functor[F[_]] {
    def map[X, Y](f: X => Y): F[X] => F[Y]
  }

  // 2. typeclass usage definition
  implicit def somethingToFunctor[F[_]: Functor, A](fa: F[A]) = new {
    val witness = implicitly[Functor[F]]
    final def map[B](f:A => B) : F[B] = witness.map(f)(fa)
  }

  // 3. creating witness object
  implicit object ArrayListFunctor extends Functor[ArrayList] {
    def map[X, Y](f: X => Y) = (xs: ArrayList[X]) => {
      val ys = new ArrayList[Y]
      for (i <- 0 until xs.size) ys.add(f(xs.get(i)))
      ys
    }
  }

Functor[F[_]]

  // 1. typeclass definition
  trait Functor[F[_]] {
    def map[X, Y](f: X => Y): F[X] => F[Y]
  }

  // 2. typeclass usage definition
  implicit def somethingToFunctor[F[_]: Functor, A](fa: F[A]) = new {
    val witness = implicitly[Functor[F]]
    final def map[B](f:A => B) : F[B] = witness.map(f)(fa)
  }

  // 3. creating witness object
  implicit object ArrayListFunctor extends Functor[ArrayList] {
    def map[X, Y](f: X => Y) = (xs: ArrayList[X]) => {
      val ys = new ArrayList[Y]
      for (i <- 0 until xs.size) ys.add(f(xs.get(i)))
      ys
    }
  }

  // 4. Use function with an object of any class that belongs to the typeclass
  val arrayList = new ArrayList(Arrays.asList(1, 2, 3))
  println(arrayList.map(_ + 1)) // => [2, 3, 4]

Typeclass pattern

  • generic interfaces...
  • that provide a common feature set...
  • over a wide variety of types

 

Typeclasses = functional design patterns

Where to go next?

  • Scala collections
  • scalaz (Haskell in Scala)
  • Functor
  • Applicative
  • Monoid
  • Monad
  • ...

Thanks

Michał Płachta

Typeclasses in a galaxy far, far away

typeclasses in a galaxy far far away

By Sir Miciek

typeclasses in a galaxy far far away

  • 1,688