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,862