slide /
Typeclasses are akin to Java interfaces, but more flexible.
We declare them in scala with traits:
trait Show[A] {
def shows(a: A): String
}
Note the subtle difference compared to the mix-in "interface" style:
trait Show {
def shows(): String
}
We also need a way to add a type to the class:
implicit val IntShow = new Show[Int] {
def shows(a: Int) = a.toString
}
We use scala implicits:
def shows[A](a : A)(implicit shower: Show[A]) = shower.shows(a)
Or alternatively, using context bounds:
def shows[A : Show](a : A) = implicitly[Show[A]].shows(a)
(implicitly is part of predef: def implicitly[A](implicit a : A) = a)
Scalaz pimps a set of common typeclasses for us, so we can just do:
3.shows // must have a "Show[Int]" instance in scope, or will fail to type check
What have we gained?
Int knows nothing about Show
def unaryShows(x : Int) : String = {
implicit val AltIntShow = new Show[Int] {
def shows(i : Int) = (1 to i) map(_ => "|") mkString
}
shows(x)
}
println shows(5) // prints '5'
println unaryShows(5) // prints '|||||'
/== Subtype polymorphismWhat we can't do with Scala's typeclasses (not easily at least):
interface Show {
String show();
}
class Foo extends Show {
public String show() {
return "a foo";
}
}
class Bar extends Show {
public String show() {
return "a bar";
}
}
List[Show] showList = new ArrayList(new Foo(), new Bar(), new Foo());
for (String s : showList) {
System.out.println(s.show);
}
A set of interrelated typeclasses that have proven handy for structuring functional code:
We'll cover functors, applicative functors, and monads
*one exception: for comprehensions and monads
A functor is simply something you can map over. It defines a single method:
trait Functor[F[_]] {
def map[A,B](fa: F[A])(f: A => B) : F[B]
}
An instance for Option:
implicit val OptionFunctor = 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
}
}
In Haskell, the arguments are reversed:
class Functor f where
fmap :: (a -> b) -> f a -> f b
In the Haskell version, we see how f is lifted into the functor context.
"Thing you can map over" leads to obvious examples:
Let's look at something less obvious: Functions!
implicit def FunctionFunctor[R] = new Functor[({type l[a] = R=>a})#l] {
def map[A, B](fa: R => A)(f: A => B) : R => B = (x => f(fa(x)))
}
Again, the Haskell version makes this much clearer. Start with fmap:
fmap :: (a -> b) -> f a -> f b
Substitute r -> a for f a
fmap :: (a -> b) -> (r -> a) -> (r -> b)
Aside: This is where the "box" analogy starts falling apart, and we start throwing around vague terms like "computational context"...
There are some additional constraints on Functors that make them "behave appropriately":
map(fa)(id) === famap(fa)(f compose g) === map(map(fa)(g))(f)These laws prevent ridiculous definitions of map, and make it possible to predict how functions behave.
Note that these laws are not enforced by the type system.
With great weakness comes great power...
Ben "Pierce" Parker
Mo' power, mo' problems...
Biggie Smalls
def fun[A](a : A) : A = ...
What is fun?
val x : List[Int] = ... def foo[F[_] : Functor](fs : F[Int]) = ...
What is foo(x).length?
You can get pretty far on map alone... but sometimes you need more.
Consider the following:
def parse(s : String) : Option[Int] = ...
Let's try to add two parsed integers with map:
parse("3").map((x: Int) => (y: Int) => x + y) // ???
We're left with an Option[Int => Int]... but what can we do with that?
map-like operationLet's add the power we need to Functor:
trait Applicative[F[_]] extends Functor {
def <*>[A, B](fa: F[A])(f : F[A => B]) : F[B]
// ... there's more to Applicative, to be shown shortly
}
Which makes our previous example look something like:
(parse("3")).<*>(parse("Nope").map(x => (y : Int) => x + y))
(Yuck)
In Haskell, things look a little more obvious:
(+) <$> parse("3") <*> parse("Nope")
Generalizing a bit, we go from calling a pure function:
f x y z
To calling a function on "effectful" arguments:
f <$> x <*> y <*> z
Scalaz's ApplicativeBuilder improves the situation somewhat:
(parse("3") |@| parse("Nope"))(_ + _)
|@|
What if some of the arguments aren't Option?
(parse("3") |@| 4 /* uh oh, no macaulay for my culkin */) (_ + _)
One small addition to our type class:
trait Applicative[F[_]] extends Functor {
def <*>[A, B](fa: F[A])(f : F[A => B]) : F[B]
def point[A](a : A): F[A]
}
Option
As before, we'll introduce a new task and see what goes wrong...
Take our parse example again:
def parse(s : String) : Option[Int] = ...
What if our input String was an optional query parameter?
val x : Option[String] = params.get("x")
We've got an Option[String] and a function String => Option[Int]
When all you have is a map hammer...
x.map(parse) // Option[Option[Int]] ??
Let's add the power we need to Applicative:
trait Monad[F[_]] extends Applicative {
def >>=[A, B](fa: F[A])(f : A => F[B]) : F[B]
}
This is pronounced bind
It makes our previous example look something like:
params.get("x") >>= parse // Option[Int]
A more complete web-based calculator example:
params.get("x") >>= parse >>= (x => (params.get("y") >>= parse) map (_ + x) )
(Yuck)
The last example hints at a fairly typical pattern:
monadicX >>= (x => monadicY >>= (y => monadicZ map (z => x+y+z)))
We have nested bind's with a final map
Reformatting it a bit:
monadicX >>= (x => monadicY >>= (y => monadicZ map (z => x+y+z )))
With Scala for comprehensions, this becomes:
for {
x <- monadicX
y <- monadicY
z <- monadicZ
} yield x + y + z
(Much nicer!)
I'll present one stab at providing an intuition for monads. Consider:
for {
x <- monadicX
y <- monadicY
} yield x + y
Q: What does this code do?
A: Depends on the monad.
A monadic for comprehension is an embedded programming language with semantics defined by the monad:
| Monad | Semantics |
|---|---|
Option |
Anonymous exceptions |
Reader |
Global environment |
Validation |
Descriptive exceptions |
List |
Nondeterministic computation |