Slide 1

Slide 1 text

State Monad Photo Credit: http://www.flickr.com/photos/yannconz/2796312310

Slide 2

Slide 2 text

Agenda • Motivate the state monad through simple examples that are familiar to developers with an imperative background

Slide 3

Slide 3 text

Simple Web Service • Develop a service that provides statistics of users on a social networking service

Slide 4

Slide 4 text

Simple Web Service trait SocialService { /** * Retrieves the follower statistics * for the specified user. */ def followerStats(username: String):FollowerStats } case class FollowerStats( username: String, numFollowers: Int, numFollowing: Int)

Slide 5

Slide 5 text

Simple Web Service object Stateless extends SocialService { def followerStats(username: String) = { // Make a call to a remote web service } }

Slide 6

Slide 6 text

Simple Web Service object Stateless extends SocialService { def followerStats(username: String) = { // Make a call to a remote web service } } Let’s cache the responses for up to 5 minutes

Slide 7

Slide 7 text

Simple Web Service object Stateful extends SocialService { private val mutableCache = ... def followerStats(username: String) = { // Check cache // If cached response exists and // it isn’t over 5 minutes old, return it; // otherwise, call web service, update // cache with response, and return response } }

Slide 8

Slide 8 text

Simple Web Service object Stateful extends SocialService { private val mutableCache = ... def followerStats(username: String) = { // Check cache // If cached response exists and // it isn’t over 5 minutes old, return it; // otherwise, call web service, update // cache with response, and return response } } How can we do this immutably?

Slide 9

Slide 9 text

Simple Web Service object Stateful extends SocialService { private val mutableCache = ... def followerStats(username: String) = { // Check cache // If cached response exists and // it isn’t over 5 minutes old, return it; // otherwise, call web service, update // cache with response, and return response } } Get from cache Write to cache Return value

Slide 10

Slide 10 text

Simple Web Service object Stateful extends SocialService { private val mutableCache = ... def followerStats(username: String) = { // Check cache // If cached response exists and // it isn’t over 5 minutes old, return it; // otherwise, call web service, update // cache with response, and return response } } Get from cache Create copy of cache with new value Return new cache and value

Slide 11

Slide 11 text

Simple Web Service trait SocialService { def followerStats(u: String, c: Cache): (Cache, FollowerStats) } Pass Cache to function Return a new cache and the value

Slide 12

Slide 12 text

Simple Web Service def followerStats(u: String, c: Cache) = { val (c1, ofs) = checkCache(u, c) ofs match { case Some(fs) => (c1, fs) case None => retrieve(u, c1) } } def checkCache(u: String, c: Cache): (Cache, Option[FollowerStats]) = ... def retrieve(u: String, c: Cache): (Cache, FollowerStats) = ...

Slide 13

Slide 13 text

Simple Web Service def checkCache(u: String, c: Cache): (Cache, Option[FollowerStats]) = { c.get(u) match { case Some(Timestamped(fs, ts)) if !stale(ts) => (c.copy(hits = c.hits + 1), Some(fs)) case other => (c.copy(misses = c.misses + 1), None) } } def stale(ts: Long): Boolean = { now - ts > (5 * 60 * 1000L) }

Slide 14

Slide 14 text

Simple Web Service def retrieve(u: String, c: Cache): (Cache, FollowerStats) = { val fs = callWebService(u) val tfs = Timestamped(fs, now) (c.update(u, tfs), fs) }

Slide 15

Slide 15 text

Explicit State Passing • Pros • Simple • Immutable • Cons • state must be manually wired through all calls - error prone!

Slide 16

Slide 16 text

State Wiring Bugs def followerStats(u: String, c: Cache) = { val (c1, ofs) = checkCache(u, c) ofs match { case Some(fs) => (c1, fs) case None => retrieve(u, c1) } } • Easy to pass the wrong value through (e.g., passing c instead of c1) • Refactoring makes this an easy mistake to make

Slide 17

Slide 17 text

Immutable State Functions

Slide 18

Slide 18 text

Immutable State Functions • Represent state as a value (immutable)

Slide 19

Slide 19 text

Immutable State Functions • Represent state as a value (immutable) • Pure functions need ways to read state, write state, and compute values

Slide 20

Slide 20 text

Immutable State Functions • Represent state as a value (immutable) • Pure functions need ways to read state, write state, and compute values • Simplest way to represent this: S 㱺 (S, A)

Slide 21

Slide 21 text

State datatype trait State[S, +A] { def run(initial: S): (S, A) def map[B](f: A => B): State[S, B] def flatMap[B](f: A => State[S, B]): State[S, B] } object State { def apply[S, A](f: S => (S, A)): State[S, A] }

Slide 22

Slide 22 text

State datatype trait State[S, +A] { def run(initial: S): (S, A) def map[B](f: A => B): State[S, B] def flatMap[B](f: A => State[S, B]): State[S, B] } object State { def apply[S, A](f: S => (S, A)): State[S, A] } Runs the function, passing initial as input

Slide 23

Slide 23 text

State datatype trait State[S, +A] { def run(initial: S): (S, A) def map[B](f: A => B): State[S, B] def flatMap[B](f: A => State[S, B]): State[S, B] } object State { def apply[S, A](f: S => (S, A)): State[S, A] } Wraps a state function in to a State[S, A] Runs the function, passing initial as input

Slide 24

Slide 24 text

State datatype trait State[S, +A] { def run(initial: S): (S, A) def map[B](f: A => B): State[S, B] def flatMap[B](f: A => State[S, B]): State[S, B] } object State { def apply[S, A](f: S => (S, A)): State[S, A] } Wraps a state function in to a State[S, A] Runs the function, passing initial as input State partially applied with one type S is a monad i.e., State[S, ?] is a monad

Slide 25

Slide 25 text

State datatype object State { def apply[S, A](f: S => (S, A)): State[S, A] = new State[S, A] { def run(i: S) = f(i) } }

Slide 26

Slide 26 text

State datatype trait State[S, +A] { def run(initial: S): (S, A) def map[B](f: A => B): State[S, B] = ??? def flatMap[B](f: A => State[S, B]): State[S, B] = ??? }

Slide 27

Slide 27 text

State datatype trait State[S, +A] { def run(initial: S): (S, A) def map[B](f: A => B): State[S, B] = State { s => val (s1, a) = run(s) (s1, f(a)) } def flatMap[B](f: A => State[S, B]): State[S, B] = ??? }

Slide 28

Slide 28 text

State datatype trait State[S, +A] { def run(initial: S): (S, A) def map[B](f: A => B): State[S, B] = State { s => val (s1, a) = run(s) (s1, f(a)) } def flatMap[B](f: A => State[S, B]): State[S, B] = State { s => val (s1, a) = run(s) f(a).run(s1) } }

Slide 29

Slide 29 text

Let’s refactor these... def followerStats(u: String, c: Cache): (Cache, FollowerStats) = ... def checkCache(u: String, c: Cache): (Cache, Option[FollowerStats]) = ... def retrieve(u: String, c: Cache): (Cache, FollowerStats) = ... Goal: use State datatype to simplify this code

Slide 30

Slide 30 text

Let’s refactor these... def followerStats(u: String, c: Cache): (Cache, FollowerStats) = ... def checkCache(u: String, c: Cache): (Cache, Option[FollowerStats]) = ... def retrieve(u: String, c: Cache): (Cache, FollowerStats) = ... Step 1: make these have the shape S => (S, A)

Slide 31

Slide 31 text

Let’s refactor these... def followerStats(u: String, c: Cache): (Cache, FollowerStats) = ... def checkCache(u: String, c: Cache): (Cache, Option[FollowerStats]) = ... def retrieve(u: String, c: Cache): (Cache, FollowerStats) = ... S S S A A A S S S Step 1: make these have the shape S => (S, A)

Slide 32

Slide 32 text

Let’s refactor these... def followerStats(u: String)(c: Cache): (Cache, FollowerStats) = ... def checkCache(u: String)(c: Cache): (Cache, Option[FollowerStats]) = ... def retrieve(u: String)(c: Cache): (Cache, FollowerStats) = ... S S S A A A S S S Step 1: make these have the shape S => (S, A)

Slide 33

Slide 33 text

Let’s refactor these... def followerStats(u: String, c: Cache) = { val (c1, ofs) = checkCache(u, c) ofs match { case Some(fs) => (c1, fs) case None => retrieve(u, c) } } Step 2: rewrite followerStats to use State

Slide 34

Slide 34 text

Let’s refactor these... def followerStats(u: String)(c: Cache) = { State(checkCache(u)) flatMap { ofs => ofs match { case Some(fs) => State { s => (s, fs) } case None => State(retrieve(u)) } }.run(c) } Step 2: rewrite followerStats to use State

Slide 35

Slide 35 text

Let’s refactor these... def followerStats(u: String)(c: Cache) = { State(checkCache(u)) flatMap { ofs => ofs match { case Some(fs) => State { s => (s, fs) } case None => State(retrieve(u)) } }.run(c) } Step 3: don’t run the state

Slide 36

Slide 36 text

Let’s refactor these... def followerStats(u: String) = { State(checkCache(u)) flatMap { ofs => ofs match { case Some(fs) => State { s => (s, fs) } case None => State(retrieve(u)) } } } Step 3: don’t run the state

Slide 37

Slide 37 text

Let’s refactor these... def checkCache(u: String)(c: Cache): (Cache, Option[FollowerStats]) = { c.get(u) match { case Some(Timestamped(fs, ts)) if !stale(ts) => (c.copy(hits = c.hits + 1), Some(fs)) case other => (c.copy(misses = c.misses + 1), None) } } Step 4: perform the same refactoring on helpers

Slide 38

Slide 38 text

Let’s refactor these... def checkCache(u: String): State[Cache, Option[FollowerStats]] = State { c => c.get(u) match { case Some(Timestamped(fs, ts)) if !stale(ts) => (c.copy(hits = c.hits + 1), Some(fs)) case other => (c.copy(misses = c.misses + 1), None) } } Step 4: perform the same refactoring on helpers

Slide 39

Slide 39 text

Let’s refactor these... def retrieve(u: String)(c: Cache): (Cache, FollowerStats) = { val fs = callWebService(u) val tfs = Timestamped(fs, now) (c.update(u, tfs), fs) } Step 4: perform the same refactoring on helpers

Slide 40

Slide 40 text

Let’s refactor these... def retrieve(u: String): State[Cache, FollowerStats] = State { c => val fs = callWebService(u) val tfs = Timestamped(fs, now) (c.update(u, tfs), fs) } Step 4: perform the same refactoring on helpers

Slide 41

Slide 41 text

Let’s refactor these... def followerStats(u: String) = { State(checkCache(u)) flatMap { ofs => ofs match { case Some(fs) => State { s => (s, fs) } case None => State(retrieve(u)) } } } Step 4: perform the same refactoring on helpers

Slide 42

Slide 42 text

Let’s refactor these... def followerStats(u: String) = { checkCache(u) flatMap { ofs => ofs match { case Some(fs) => State { s => (s, fs) } case None => retrieve(u) } } } Step 4: perform the same refactoring on helpers

Slide 43

Slide 43 text

Let’s refactor these... def followerStats(u: String) = { checkCache(u) flatMap { ofs => ofs match { case Some(fs) => State { s => (s, fs) } case None => retrieve(u) } } } Step 5: take advantage of rewritten helpers

Slide 44

Slide 44 text

Let’s refactor these... def followerStats(u: String) = for { ofs <- checkCache(u) fs <- ofs match { case Some(fs) => State { s => (s, fs) } case None => retrieve(u) } } yield fs Step 5: take advantage of rewritten helpers

Slide 45

Slide 45 text

State datatype • Pros • simple • immutable • automatic state wiring • for-comprehensions let us write imperative looking code while maintaining these benefits

Slide 46

Slide 46 text

State Combinators

Slide 47

Slide 47 text

State Combinators def followerStats(u: String) = for { ofs <- checkCache(u) fs <- ofs match { case Some(fs) => State { s => (s, fs) } case None => retrieve(u) } } yield fs Constructs a State[S, A] for some value of A

Slide 48

Slide 48 text

State Combinators def state[S, A](a: A): State[S, A] = State { s => (s, a) } Constructs a State[S, A] for some value of A

Slide 49

Slide 49 text

Let’s refactor these... def followerStats(u: String) = for { ofs <- checkCache(u) fs <- ofs match { case Some(fs) => State.state(fs) case None => retrieve(u) } } yield fs

Slide 50

Slide 50 text

Let’s refactor these... def followerStats(u: String) = for { ofs <- checkCache(u) fs <- ofs. map(State.state[Cache, FollowerStats]). getOrElse(retrieve(u)) } yield fs

Slide 51

Slide 51 text

State Combinators

Slide 52

Slide 52 text

State Combinators def get[S]: State[S, S] = State { s => (s, s) }

Slide 53

Slide 53 text

State Combinators def get[S]: State[S, S] = State { s => (s, s) } def gets[S, A](f: S => A): State[S, A] = State { s => (s, f(s)) }

Slide 54

Slide 54 text

State Combinators def get[S]: State[S, S] = State { s => (s, s) } def gets[S, A](f: S => A): State[S, A] = State { s => (s, f(s)) } def put[S](s: S): State[S, Unit] = State { _ => (s, ()) }

Slide 55

Slide 55 text

State Combinators def get[S]: State[S, S] = State { s => (s, s) } def gets[S, A](f: S => A): State[S, A] = State { s => (s, f(s)) } def put[S](s: S): State[S, Unit] = State { _ => (s, ()) } def modify[S](f: S => S): State[S, Unit] = State { s => (f(s), ()) }

Slide 56

Slide 56 text

Let’s refactor these... def checkCache(u: String): State[Cache, Option[FollowerStats]] = State { c => c.get(u) match { case Some(Timestamped(fs, ts)) if !stale(ts) => (c.copy(hits = c.hits + 1), Some(fs)) case other => (c.copy(misses = c.misses + 1), None) } }

Slide 57

Slide 57 text

Let’s refactor these... def checkCache(u: String): State[Cache, Option[FollowerStats]] = for { c <- State.get[Cache] ofs <- State.state { c.get(u).collect { case Timestamped(fs, ts) if !stale(ts) => fs } } _ <- State.put(ofs ? c.recordHit | c.recordMiss) } yield ofs

Slide 58

Slide 58 text

Let’s refactor these... def checkCache(u: String): State[Cache, Option[FollowerStats]] = for { c <- State.get[Cache] ofs <- State.state { c.get(u).collect { case Timestamped(fs, ts) if !stale(ts) => fs } } _ <- State.put(ofs ? c.recordHit | c.recordMiss) } yield ofs What’s the potential bug with get at top and put at bottom?

Slide 59

Slide 59 text

Let’s refactor these... def checkCache(u: String): State[Cache, Option[FollowerStats]] = for { ofs <- State.gets { c: Cache => c.get(u).collect { case Timestamped(fs, ts) if !stale(ts) => fs } } _ <- State.modify { c: Cache => ofs ? c.recordHit | c.recordMiss } } yield ofs

Slide 60

Slide 60 text

Let’s refactor these... def retrieve(u: String): State[Cache, FollowerStats] = State { c => val fs = callWebService(u) val tfs = Timestamped(fs, now) (c.update(u, tfs), fs) }

Slide 61

Slide 61 text

Let’s refactor these... def retrieve(u: String): State[Cache, FollowerStats] = for { fs <- State.state(callWebService(u)) tfs = Timestamped(fs, now) _ <- State.modify[Cache] { _.update(u, tfs) } } yield fs

Slide 62

Slide 62 text

scalaz.State

Slide 63

Slide 63 text

scalaz.State • Scalaz provides scalaz.State datatype

Slide 64

Slide 64 text

scalaz.State • Scalaz provides scalaz.State datatype • All examples so far compile with Scalaz

Slide 65

Slide 65 text

scalaz.State • Scalaz provides scalaz.State datatype • All examples so far compile with Scalaz • Benefits of using Scalaz version: • Typeclass integration • Lens integration • Monad transformers

Slide 66

Slide 66 text

eval and exec

Slide 67

Slide 67 text

eval and exec • In addition to run, there are two other convenient ways to run a stateful computation:

Slide 68

Slide 68 text

eval and exec • In addition to run, there are two other convenient ways to run a stateful computation: • eval - Ignore output state (S), resulting in a value (A) def eval(initial: S): A

Slide 69

Slide 69 text

eval and exec • In addition to run, there are two other convenient ways to run a stateful computation: • eval - Ignore output state (S), resulting in a value (A) def eval(initial: S): A • exec - Ignore output value (A), resulting in a state (S) def exec(initial: S): S

Slide 70

Slide 70 text

eval and exec • Given a monoid for S, it’s common to use the zero element as the initial state • Scalaz provides conveniences for this given an implicitly scoped Monoid[S] def runZero(implicit S: Monoid[S]): (S, A) def evalZero(implicit S: Monoid[S]): A def execZero(implicit S: Monoid[S]): S

Slide 71

Slide 71 text

Typeclass Integration • State[Cache, ?] has default implementations of: • Pointed • Functor • Applicative • Monad

Slide 72

Slide 72 text

Typeclass Integration type StateCache[+A] = State[Cache, A] Pointed[StateCache].point(10) import scalaz.syntax.id._ 10.point[StateCache] Functor[StateCache].map(ten) { _.toString } // etc.

Slide 73

Slide 73 text

Typeclass Integration import scalaz.syntax.traverse._ import scalaz.std.list._ val listOfState: List[StateCache[FollowerStats]] = List(s.followerStats("u1"), s.followerStats("u2"), s.followerStats("u1")) val stateOfList: StateCache[List[FollowerStats]] = listOfState.sequence[StateCache, FollowerStats] stateOfList.run(Cache.empty) Resultant cache has 2 misses and 1 hit

Slide 74

Slide 74 text

State Transforms

Slide 75

Slide 75 text

State Transforms

Slide 76

Slide 76 text

State Transforms • So far, we don’t have a way to manipulate state data

Slide 77

Slide 77 text

State Transforms • So far, we don’t have a way to manipulate state data • Suppose application is structured as State[MyApplicationState, ?]

Slide 78

Slide 78 text

State Transforms • So far, we don’t have a way to manipulate state data • Suppose application is structured as State[MyApplicationState, ?] • We don’t want to fix every stateful function in the app to use MyApplicationState as state type

Slide 79

Slide 79 text

State Transforms • So far, we don’t have a way to manipulate state data • Suppose application is structured as State[MyApplicationState, ?] • We don’t want to fix every stateful function in the app to use MyApplicationState as state type • We want to compose state functions of “smaller” state types in to functions of “larger” state types

Slide 80

Slide 80 text

State Transforms • So far, we don’t have a way to manipulate state data • Suppose application is structured as State[MyApplicationState, ?] • We don’t want to fix every stateful function in the app to use MyApplicationState as state type • We want to compose state functions of “smaller” state types in to functions of “larger” state types • Problem: given State[T, ?] how can we treat it as State[S, ?]

Slide 81

Slide 81 text

State Transforms def lift[T,S,A]( s: State[T, A] ): State[S, A] = { ??? }

Slide 82

Slide 82 text

State Transforms def lift[T,S,A]( s: State[T, A] ): State[S, A] = { State { inputS => val inputT = ??? val (outputT, a) = s.run(inputT) val outputS = ??? (outputS, a) } }

Slide 83

Slide 83 text

State Transforms def lift[T,S,A]( s: State[T, A] ): State[S, A] = { State { inputS => val inputT = get(inputS) val (outputT, a) = s.run(inputT) val outputS = set(s, outputT) (outputS, a) } }

Slide 84

Slide 84 text

State Transforms def lift[T, S, A]( s: State[T, A], get: S => T, set: (S, T) => S ): State[S, A] = { State { inputS => val inputT = get(inputS) val (outputT, a) = s.run(inputT) val outputS = set(inputS, outputT) (outputS, a) } }

Slide 85

Slide 85 text

State Transforms def lift[T, S, A]( s: State[T, A], l: Lens[S, T] ): State[S, A] = { State { inputS => val inputT = l.get(inputS) val (outputT, a) = s.run(inputT) val outputS = l.set(inputS, outputT) (outputS, a) } }

Slide 86

Slide 86 text

State Transforms def lift[T, S, A]( s: State[T, A], l: Lens[S, T] ): State[S, A] = { l.lifts(s) }

Slide 87

Slide 87 text

State Transforms sealed trait LensT[F[+_], A, B] { ... def lifts[C]( s: StateT[F, B, C] )(implicit M: Bind[F]): StateT[F, A, C] }

Slide 88

Slide 88 text

Integrating State and Either

Slide 89

Slide 89 text

Either Integration trait Model trait StatsQuery trait QueryResult def runQuery(s: String, m: Model): String \/ QueryResult = for { query <- parseQuery(s) res <- performQuery(query, m) } yield res def parseQuery(s: String): String \/ StatsQuery = "TODO".left def performQuery(q: StatsQuery, m: Model): String \/ QueryResult = "TODO".left

Slide 90

Slide 90 text

Either Integration trait Model trait StatsQuery trait QueryResult def runQuery(s: String, m: Model): String \/ QueryResult = for { query <- parseQuery(s) res <- performQuery(query, m) } yield res def parseQuery(s: String): String \/ StatsQuery = "TODO".left def performQuery(q: StatsQuery, m: Model): String \/ QueryResult = "TODO".left Requirements changed and now parse and peform need to be stateful!

Slide 91

Slide 91 text

Either Integration trait QueryState type QueryStateS[+A] = State[QueryState, A] def runQuery(s: String, m: Model): QueryStateS[String \/ QueryResult] = for { query <- parseQuery(s) res <- performQuery(query, m) } yield res def parseQuery(s: String): QueryStateS[String \/ StatsQuery] = ... def performQuery(q: StatsQuery, m: Model): QueryStateS[String \/ QueryResult] = ... Wrap result types in State

Slide 92

Slide 92 text

Either Integration trait QueryState type QueryStateS[+A] = State[QueryState, A] def runQuery(s: String, m: Model): QueryStateS[String \/ QueryResult] = for { query <- parseQuery(s) res <- performQuery(query, m) } yield res def parseQuery(s: String): QueryStateS[String \/ StatsQuery] = ... def performQuery(q: StatsQuery, m: Model): QueryStateS[String \/ QueryResult] = ...

Slide 93

Slide 93 text

Either Integration trait QueryState type QueryStateS[+A] = State[QueryState, A] def runQuery(s: String, m: Model): QueryStateS[String \/ QueryResult] = for { query <- parseQuery(s) res <- performQuery(query, m) } yield res def parseQuery(s: String): QueryStateS[String \/ StatsQuery] = ... def performQuery(q: StatsQuery, m: Model): QueryStateS[String \/ QueryResult] = ... query is String \/ StatsQuery now!

Slide 94

Slide 94 text

Either Integration We need to combine the effects of Either and State!

Slide 95

Slide 95 text

EitherT trait EitherT[F[+_], +A, +B] { val run: F[A \/ B] ... } • EitherT represents a value • EitherT is a monad transformer, which lets us compose effects of Either with effects of an arbitrary monad • We won’t cover the theory of monad transformers See this fantastic talk: http://goo.gl/D6rt1 Full URL: http://marakana.com/s/video_monad_tranformers_in_scalamachine_scaliak,1232/index.html F[A \/ B]

Slide 96

Slide 96 text

EitherT trait EitherT[F[+_], +A, +B] { ... } • EitherT represents a value • EitherT is a monad transformer, which lets us compose effects of Either with effects of an arbitrary monad • We won’t cover the theory of monad transformers See this fantastic talk: http://goo.gl/D6rt1 Full URL: http://marakana.com/s/video_monad_tranformers_in_scalamachine_scaliak,1232/index.html F[A \/ B]

Slide 97

Slide 97 text

EitherT trait EitherT[F[+_], +A, +B] { ... } F[A \/ B]

Slide 98

Slide 98 text

EitherT trait EitherT[F[+_], +A, +B] { ... } F[A \/ B] QueryStateS[String \/ QueryResult] We have:

Slide 99

Slide 99 text

EitherT trait EitherT[F[+_], +A, +B] { ... } F[A \/ B] QueryStateS[String \/ QueryResult] EitherT[QueryStateS, String, QueryResult] We have: So we can use:

Slide 100

Slide 100 text

EitherT Example type QueryStateS[+A] = State[QueryState, A] type ET[F[+_], A] = EitherT[F, String, A] type QueryStateES[A] = ET[QueryStateS, A] object QueryStateES { def apply[A](s: QueryStateS[String \/ A]): QueryStateES[A] = EitherT(s) def liftE[A](e: String \/ A): QueryStateES[A] = apply(Pointed[QueryStateS].point(e)) def liftS[A](s: QueryStateS[A]): QueryStateES[A] = MonadTrans[ET].liftM(s) }

Slide 101

Slide 101 text

EitherT Example type QueryStateS[+A] = State[QueryState, A] type ET[F[+_], A] = EitherT[F, String, A] type QueryStateES[A] = ET[QueryStateS, A] object QueryStateES { def apply[A](s: QueryStateS[String \/ A]): QueryStateES[A] = EitherT(s) def liftE[A](e: String \/ A): QueryStateES[A] = apply(Pointed[QueryStateS].point(e)) def liftS[A](s: QueryStateS[A]): QueryStateES[A] = MonadTrans[ET].liftM(s) } def liftM[G[_] : Monad, A](a: G[A]): F[G, A]

Slide 102

Slide 102 text

EitherT Example def runQuery(s: String, m: Model): QueryStateES[QueryResult] = for { query <- parseQuery(s) res <- performQuery(query, m) } yield res def parseQuery(s: String): QueryStateES[StatsQuery] = ... def performQuery(q: StatsQuery, m: Model): QueryStateES[QueryResult] = ... runQuery(s, m).run.run(initialQueryState)

Slide 103

Slide 103 text

StateT

Slide 104

Slide 104 text

Looking Closer type State[S, +A] = StateT[Id, S, A] object State extends StateFunctions { def apply[S, A](f: S => (S, A)): State[S, A] = new StateT[Id, S, A] { def apply(s: S) = f(s) } }

Slide 105

Slide 105 text

Looking Closer trait StateT[F[+_], S, +A] { ... } type Id[+X] = X type State[S, +A] = StateT[Id, S, A] • StateT represents a function S => F[(S, A)] • Id type alias lets us use StateT as State • StateT is a monad transformer

Slide 106

Slide 106 text

StateT Example val getAndIncrement: State[Int, Int] = State { s => (s + 1, s) } getAndIncrement.replicateM(10).evalZero

Slide 107

Slide 107 text

StateT Example val getAndIncrement: State[Int, Int] = State { s => (s + 1, s) } getAndIncrement.replicateM(10).evalZero def replicateM(n: Int): F[List[A]] Think: flatMap(flatMap(flatMap(...)))

Slide 108

Slide 108 text

StateT Example val getAndIncrement: State[Int, Int] = State { s => (s + 1, s) } getAndIncrement.replicateM(10).evalZero List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

Slide 109

Slide 109 text

StateT Example val getAndIncrement: State[Int, Int] = State { s => (s + 1, s) } getAndIncrement.replicateM(100000).evalZero

Slide 110

Slide 110 text

StateT Example val getAndIncrement: State[Int, Int] = State { s => (s + 1, s) } getAndIncrement.replicateM(100000).evalZero StackOverflowError!

Slide 111

Slide 111 text

StateT Example • Stack overflows because there are 10,000 nested flatMap calls occurring • Scalaz provides the Free datatype, which when used with Function0, trades heap for stack type Trampoline[+A] = Free[Function0, A] • How can we combine the effects of Trampoline with State?

Slide 112

Slide 112 text

StateT Example val getAndIncrement: State[Int, Int] = State { s => (s + 1, s) } import scalaz.Free.Trampoline getAndIncrement.lift[Trampoline]. replicateM(100000).evalZero.run For details on Free/Trampoline: http://days2012.scala-lang.org/sites/days2012/files/bjarnason_trampolines.pdf

Slide 113

Slide 113 text

StateT Example val getAndIncrement: State[Int, Int] = State { s => (s + 1, s) } import scalaz.Free.Trampoline getAndIncrement.lift[Trampoline]. replicateM(100000).evalZero.run def lift[M[_]: Pointed]: StateT[M[F[?]], S, A] For details on Free/Trampoline: http://days2012.scala-lang.org/sites/days2012/files/bjarnason_trampolines.pdf

Slide 114

Slide 114 text

StateT Example val getAndIncrement: State[Int, Int] = State { s => (s + 1, s) } import scalaz.Free.Trampoline getAndIncrement.lift[Trampoline]. replicateM(100000).evalZero.run def lift[M[_]: Pointed]: StateT[M[F[?]], S, A] StateT[Trampoline[Id], Int, Int]

Slide 115

Slide 115 text

Questions? Photo Credit: http://www.flickr.com/photos/yannconz/2795462449