Typeclasses
put to work
What are typeclasses?
"If an object can bark and wag its tail, then it is a dog."
This approach seems to work well with interfaces/traits.
Typeclasses are interfaces that a type doesn't have to be aware of.
This means that you can take someone else's code and make their types fit your framework.

source: dailymail.co.uk
Quick example
Following the dog analogy:
if objects of a given type can be combined together and the combination has a netural element within this type, then this type is a Monoid.
trait Monoid[A] { // this trait is the actual typeclass
def zero: A // the neutral element
def append(a: A, b: A): A // the combining function
}Instance declaration:
case class Log(msgs: List[String], ids: List[Int])
implicit val logMonoid = new Monoid[Log] {
def zero = Log(List.empty, List.empty)
def append(a: Log, b: Log): Log = (a, b) match {
case (Log(msgsA, idsA), Log(msgsB, idsB)) => Log(msgsA ++ msgsB, idsA ++ idsB)
}
}Quick example
However, its usage is not really Scala-style. That's why the following is a ubiquitous pattern in scalaz:
trait MonoidOps[A] {
val M: Monoid[A]
val value: A
def <+>(a: A): A = M.append(value, a)
}
implicit def toMonoidOp[A: Monoid](a: A): MonoidOps[A] = new MonoidOps[A] {
val M = implicitly[Monoid[A]]
val value = a
}Now that's a lot better:
val l1 = Log(List("Start", "Hello"), List(0, 1))
val l2 = Log(List("World", "Morning"), List(2, 3))
scala> l1 <+> l2
res0: Log = Log(List("Start", "Hello", "World", "Morning"), List(0, 1, 2, 3))Even quicker example
What about instances for types with parameters?
sealed trait MyOption[+T]
case class MySome[+T](x: T) extends MyOption[T]
case object MyNone extends MyOption[Nothing]
// note the "def" here
implicit def myOptionShow[A: Show]: Show[MyOption[A]] = new Show[MyOption[A]] {
override def show(mo: MyOption[A]): Cord = mo match {
case MySome(a) => Cord("MySome(", Show[A].show(a), ")")
case MyNone => Cord("MyNone")
}
}For instance, when making a Show instance (for converting things to string), we'd like to recursively show all the members of the type.
Here's an instance for our own Option-like type:
Map lookup
val ips = Map("www.google.com" -> "216.58.209.68",
"www.onet.pl" -> "213.180.141.140",
"www.scala-lang.org" -> "128.178.154.159",
"totally-fake.co.uk" -> "432.321.212.333",
"even-faker.se" -> "123.123",
"www.what.pl" -> "Ala ma kota")The value might not be there, i.e. there might be one or zero values associated with a given key.
scala> ips get "www.google.com"
// res0: Option[String] = Some(216.58.209.68)
scala> ips get "www.youtube.com"
// res1: Option[String] = NoneLet's make ourselves a map:
Map lookup (cont.)
How to operate on the returned value?
Idea #1 --> extract it:
result match {
case Some(addr) => someProcessing(addr)
case None => ohNo()
}Downside: it's cool when used only once or so.
Idea #2 --> map:
result map { res => someProcessing(res) }Upside: it's cool also when chained.
Downside: chaining doesn't work if someProcessing returns Option as well (back to that later on).
Functor
A thing that can be mapped over, a container.
Some examples: List, Option, Future, Either, etc.
trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
//...
}Sample instance for Option:
implicit val optionFunctor: Functor[Option] = new Functor[Option] {
def map[A, B](fa: Option[A])(f: A => B): Option[B] = fa match {
case Some(a) => Some(f(a))
case None => None
}
}Functor
For many typeclasses scalaz provides some convenience methods.
For Functor this is for example an infix version of map:
final class FunctorOps[F[_],A] (val self: F[A])(implicit val F: Functor[F]) extends Ops[F[A]] {
//...
final def map[B](f: A => B): F[B] = F.map(self)(f)
//...
}Along with some conversions:
trait ToFunctorOps extends ToFunctorOps0 with ToInvariantFunctorOps {
implicit def ToFunctorOps[F[_],A](v: F[A])(implicit F0: Functor[F]) =
new FunctorOps[F,A](v)
//...
}Back to the Map
Map is cool only for so long. What if the function you are mapping returns Option as well?
Say we want to convert a string to an IP address:
class IPAddr(val addr: (Int, Int, Int, Int))
object IPAddr {
def fromString(s: String): Option[IPAddr] = ...
//...
}Mapping fromString over the result of get yields:
(ips get "www.google.com") map IPAddr.fromString // type is: Option[Option[IPAddr]]What now? Pattern matching seems painful, mapping again even more so.
flatMap
scala> val list = List(1, 2, 3, 4, 5)
// list: List[Int] = List(1, 2, 3, 4, 5)scala> list flatMap (x => List.fill(3)(x))
// res0: List[Int] = List(1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5)
Works fine on lists:
scala> val m = Some(5)
// m: Option[Int] = Some(5)scala> m flatMap (x => Some(x + 5))
// res1: Option[Int] = Some(10)
Apparently on Options as well:
flatMap (cont.)
ips.get("www.google.com").flatMap(IPAddr.fromString) // type is Option[IPAddr]It also solves out problem:
As innocent as it looks, it actually enables a very powerful mechanism.
Monad
A way of chaining effectful computation in an almost imperative, sequential manner, at the same time complying with the functional paradigm.
Every monad supports at least two operations:
- return (unit) -- puts the value in the monad (a -> m a)
- bind (flatMap) -- takes a function (a -> m b) and applies it to value inside a monad, thus has the type: (a -> m b) -> m a -> m b
Monad (cont.)
Why is flatMap so special?
Because it composes in a very particular way:
val s = Some(10)
s flatMap ( x => // x binds to 10
Some(x + 5) flatMap (y => // y binds to 15
Some(x + y) flatMap (z => // z binds to 25
Some(x*y*z)))) // the result is 3750Let's rewrite that:
val s = Some(10)
s flatMap (x => Some(x + 5) flatMap (y => Some(x + y) flatMap (z => Some(x*y*z))))Or:
for {
x <- s
y <- Some(x + 5)
z <- Some(x + y)
} yield (x * y * z)Monad (cont.)
Ok, so there we have bind. We can also use the operator from scalaz:
val s: Option[Int] = Some(10)
val f: Int => Option[Int] = x => Some(x + 5)
scala> s >>= f
// res0: Option[Int] = Some(15)
scala> ips get "www.google.com" >>= IPAddr.fromString
// res1: Option[IPAddr] = Some(IPAddr(216.58.209.68))There's also return, for putting the value in monadic context. It's named differently for many reasons, one being the obvious name clash.
scala> 10.point[Option]
res0: Option[Int] = Some(10)
scala> 10.pure[Option] // much better name :)
res1: Option[Int] = Some(10)Monad (cont.)
Scalaz defines Monad with lots of other features, along with some popular monads like:
- Reader -- provides a read-only environment, can be used to implement dependency injection
- Writer -- for constructing log-like structures alongside the computations
- Option, List -- handle non-determinism
- Validation, Either -- handle errors
- State -- for, well, state
Back to Map again
Remember the IP address map?
class IPAddr(val addr: (Int, Int, Int, Int)) {
def mask(m: IPAddr): IPAddr = IPAddr(addr._1 & m.addr._1,
addr._2 & m.addr._2,
addr._3 & m.addr._3,
addr._4 & m.addr._4)
}We parse the address, parse the mask and want to call mask on them. But they are both Options... Pattern match?
val addr = ips get "www.google.com" >>= IPAddr.fromString // Option[IPAddr]
val m = IPAddr.fromString("255.255.255.0") // Option[IPAddr]
val masked = (addr, m) match {
case (Some(a), Some(m)) => Some(a.mask(m))
case _ => None
Let's add a functionality for masking the IP addresses:
Do we give up?
Ok, we can map, so let's try mapping:
scala> addr map { (x: IPAddr) => (y: IPAddr) => x.mask(y) }
// res0: Option[IPAddr => IPAddr] = Some(<function1>)So far so good, we have applied the function to the address, but what about the mask?
What we have now is something like this:
Some( (y: IPAddr) => mask(y) )Don't even think about it, no pattern matching.
Applicative
Turns out there's a typeclass for that:
trait Applicative[F[_]] extends Functor {
def <*>[A, B](fa: F[A])(f : F[A => B]) : F[B]
def point[A](a : A): F[A] // pure
}If we look at the type of <*>, it's exactly what we need:
val addr = ips get "www.google.com" >>= IPAddr.fromString
scala> IPAddr.fromString("255.255.255.0") <*> addr.map {
(x: IPAddr) => (y: IPAddr) => x.mask(y)
}
// res1: Option[IPAddr] = Some(IPAddr(216.58.209.0))If the mask function took more arguments, we would just need to use <*> more times.
Applicative
This is called ApplicativeBuilder, and it provies an easy way to apply a function of multiple arguments in Applicative context (although the application is postfix):
def f(x: IPAddr, y: IPAddr, z: IPAddr): IPAddr = x.mask(y).mask(z)
scala> (IPAddr.fromString("192.168.10.1") |@|
IPAddr.fromString("255.255.255.0") |@|
IPAddr.fromString("255.255.255.128"))(f)
// res0: Option[IPAddr] = Some(IPAddr(192.168.10.0))The need for Applicative in Scala is not very obvious, but it can serve as a nice alternative to Monad when you don't need all the power.
// hmmm...
(addr |@| IPAddr.fromString("255.255.255.0"))(_.mask(_))Let's take a look
Typeclasses are a concept borrowed from Haskell and we are squeezing them into the Scala ecosystem, however not without some sacrifice:
val f = x => x + 5Q: What's the type of:
A: That doesn't even compile. However:
> let f = \x -> x + 5
> :t f
f :: Num a => a -> aThis means that the Scala compiler cannot infer the typeclass based on the function used. In general, Scala's type inference may be a disappointment sometimes.
And that's sad

source: pl.wikipedia.org
Cheer up
It's not like we aren't getting anything in return.
def f() {
import scalaz.syntax.std.option
// some option stuff...
}Scoped imports allow you to toggle the instances on and off, shadow them when you want or restrict the scope of the instance.
Also, every Monad is an Applicative (!)
trait Monad[F[_]] extends Applicative[F] with Bind[F] { ... }Wrapping up
We've only scratched the surface here.
For further learning, please check out:
http://typeclassopedia.bitbucket.org/
http://eed3si9n.com/learning-scalaz
http://learnyouahaskell.com/chapters (yup, really)
http://blog.originate.com/blog/2013/10/21/reader-monad-for-dependency-injection/
Thanks!
Typeclasses demystified
By ambrozyk
Typeclasses demystified
- 1,043