Thinking Monadically in Scala (:)
Programming with Scalaz
- Yuvi Masory, Combinatory Solutions Inc
- @ymasory
- http://yuvimasory.com/talks
- May 29, 2012
- Philly Lambda
- A few bits from Mike Pilquist’s Scalaz talk
"1".toInt
"foo".toInt //uh-oh
val iOpt: Option[Int] = "foo".parseInt
iOpt err "not an int!"
println(iOpt.isDefined ? "parsed" | "unparseable")
==
Considered Harmfulval admin: Option[User] = //...
val curUser: User = //...
if (curUser == admin) { //oops! always false
//...
}
===
Considered Awesomeimplicit def userEqual = equalA[User]
if (curUser === admin) { //doesn't compile!
//...
}
You may have noticed this strange bit
implicit def userEqual = equalA[User]
A simple definition: types are collections of expressions.
Because we want our functions to work on many types.
Polymorphic functions may do something different depending on its input type.
trait Equal[A] {
def ===(a: A): Boolean
}
case class Person(name: String, zip: Int) extends Equal[Person] {
def ===(that: Person) = //...
}
Person("yuvi", 19104) === Person("colleen", 12345)
Person
source code?
trait Equal[A] {
// A => A => Boolean
def equals(a1: A, a2: A): Boolean
}
def personEqual: Equal[Person] = new Person {
def equals(p1: Person, p2: Person): Boolean = //...
}
personEqual equals (Person("yuvi", 19104), Person("colleen", 12345))
trait Equal[A] {
def(lhs: A, rhs: A): Boolean
}
object Equal {
implicit def addEqualOps[A:Equal](lhs: A) = new EqualOps(lhs)
}
class EqualOps[A](lhs: A)(implicit ev: Equal[A]) {
def ===(rhs: A) ev.equals(lhs, rhs)
}
implicit object PersonEqual extends Equal[Person] {
def equals(p1: Person, p2: Person): Boolean = //...
}
import Equal._
p1 === p2
//"is-a"
def mycompare[A <: Comparable](lhs: A, rhs: A) = lhs compare rhs
//"has-a"
def myequal[A:Equal](lhs: A, rhs: A) = lhs equals rhs
//"has-a", de-sugared, note "ev" is never used
def myequal[A](lhs: A, rhs: A)(ev: Equal[A]) = lhs equals rhs
Things you can add.
// A => A => A
// Int => Int => Int
trait Semigroup[A] {
def plus[A](s1: A, s2: A): A
}
// A
trait Monoid[A] extends Semigroup[A] {
def zero[A]: A
}
Let’s keep it simple: if you can map over it, it’s a functor.
// map: F[A] => (A => B) => F[B]
// e.g., Option[Int] => (Int => String) => Option[String]
trait Functor[F[_]] {
def map[A, B](r: F[A], f: A => B): F[B]
}
Option
has a Functor
object OptionIsFunctor: Functor[Option] = new Functor[Option] {
def map[A, B](r: Option[A], f: A => B) = r match {
case None => None
case Some(a) => Some(f(a))
}
}
List
has a Functor
object ListIsFunctor: Functor[List] = new Functor[List] {
def map[A, B](as: List[A], f: A => B) = as match {
case Nil => Nil
case h :: t => f(h) :: map(t, f)
}
}
Functor
for (el <- List(1, 2)) yield el + 10
List(1, 2) map { _ + 10 }
trait Applicative[F[_]] extends Functor[F] {
def pure(a: A): A
def apply...
}
// why aren't we making use of strict order?
for {
url <- urlOpt
pw <- passwordOpt
uname <- usernameOpt
} yield DriverManager getConnection (url, pw, uname)
// now we're not
(url |@| pw |@| uname) { Driver.getConnection }
Monads are like elephants? Pirates?
You can’t handle an A
, so I’ll give you an M[A]
for some monad M
.
def perfectRoot(i: Int): Option[Int] = //...
//shouldn't this be of type Int => Option[Int]?
def doublePerfectRoot = perfectRoot compose perfectRoot
//how do we go about this?
val opt = perfectRoot(10000) //Some(100)
opt map { perfectRoot(_) } //Some(Some(10)), fail :(
Option
has a Monad
def perfectRoot(i: Int): Option[Int] = //...
val opt = perfectRoot(10000)
opt map { perfectRoot(_) } //Some(100)
opt flatMap { perfectRoot(_) } //Some(10), yay!
Monads = applicative functors that can flatMap
trait Monad[M[_]] extends Applicative[M] {
// A => M[A]
// e.g., Int => List[Int]
def pure[A](a: => A): M[A]
// M[A] => (A => M[B]) => M[B]
// e.g., List[Int] => (Int => List[Int]) => List[Int]
def flatMap[A, B](a: M[A], f: A => M[B]): M[B]
}
List[B]
- what’s wrong with A -> B
?
Option[B]
- what’s wrong with A -> B
?
Function1[B]
- what’s wrong with A -> B
?
Monad
for {
a <- aOpt
b <- bOpt
c <- cOpt
} yield a + b + c
aOpt.flatMap { a =>
bOpt.flatMap { b =>
cOpt.map { c =>
a + b + c
}
}
}
IO[A]
- what’s wrong with () -> A
?
def now: Long
def pnow: IO[Long]
val n1 = now
val n2 = now
n2 - n1
IO
in actionscala> println("hello, world")
hello, world
scala> putStrLn("hello, world")
res1: scalaz.effects.IO[Unit] = scalaz.effects.IO$$anon$2@60419710
IO
typeclasstrait IO[A] {
// A
// "don't call until the end of the universe"
def unsafePerformIO: A
}
object CatsWithHatsApp {
def addHat(p: Picture): Picture = ...
def pmain(args: Vector[String]): IO[Unit] = {
val Vector(path) = args
for {
files <- listDir(path) //List[File] <- IO[List[File]]
file <- files //File <- List[File]
pic <- readPic(file) //Picture <- IO[Picture]
_ <- writeFile( //() <- IO[Unit]
file,
addHat(pic)
)
} yield ()
}
def main(args: Array[String]) = {
val program = pmain.(Vector.empty ++ args).except {
case e => putStrLn("failing with: " e.toString)
}
program.unsafePerformIO //end of the universe!
}
}
ApplicativePlus
, MonadPlus
, Comonad
, Category
, Arrow
, ArrowPlus
, Foldable
, Traversable
, Monad Transformers
, Reader
, Writer
, State
, Identity
, and more
These are functional “design patterns.”