Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Index your State for safer functional APIs

Index your State for safer functional APIs

Vincent Marquez

May 29, 2016
Tweet

More Decks by Vincent Marquez

Other Decks in Programming

Transcript

  1. Have you ever wondered about the Scalaz State?
 found :

    scalaz.State[Int,String] (which expands to) scalaz.IndexedStateT[scalaz.Id.Id,Int,Int,String]
  2. Let’s look at the source:
 sealed abstract class IndexedStateT[F[_], -S1,

    S2, A] { self => def getF[S <: S1]: Monad[F] => F[S => F[(S2, A)]] ! /** Run and return the final value and state in the context of `F` */ def apply(initial: S1)(implicit F: Monad[F]): F[(S2, A)] = F.join(F.map[S1 => F[(S2, A)], F[(S2, A)]](getF(F))(sf => sf(initial))) …
  3. ! What is a State Monad? ! LYAHFGG: We’ll say

    that a stateful computation is a function that takes some state and returns a value along with some new state. That function would have the following type: ! A => (A, B)
 ! ! !
  4. Simple State ! case class SState[A, B](f: (A => (A,B)))

    {! ! ! ! def flatMap[C](fc: B => SState[A, C]): SState[A, C] = ! ! ! ! ! ! SState((a: A) => f(a) match {! case (a, b) => fc(b).f(a)! })! }! ! !
  5. Simple State flatMap ! case class SState[A, B](f: (A =>

    (A,B))) {! ! ! ! def flatMap[C](fc: B => SState[A, C]): SState[A, C] = ! ! ! ! ! ! SState((a: A) => f(a) match {! case (a, b) => fc(b).f(a)! })! }! ! !
  6. Simple State flatMap ! case class SState[A, B](f: (A =>

    (A,B))) {! ! ! ! def flatMap[C](fc: B => SState[A, C]): SState[A, C] = ! ! ! ! ! ! SState((a: A) => f(a) match {! case (a, b) => fc(b).f(a)! })! }! ! !
  7. Simple State flatMap ! case class SState[A, B](f: (A =>

    (A,B))) {! ! ! ! def flatMap[C](fc: B => SState[A, C]): SState[A, C] = ! ! ! ! ! ! SState((a: A) => f(a) match {! case (a, b) => fc(b).f(a)! })! }! ! !
  8. Simple State flatMap ! case class SState[A, B](f: (A =>

    (A,B))) {! ! ! ! def flatMap[C](fc: B => SState[A, C]): SState[A, C] = ! ! ! ! ! ! SState((a: A) => f(a) match {! case (a, b) => fc(b).f(a)! })! }! ! !
  9. State example: Counter ! ! def countState = SState[Int, String]((i:

    Int) => ! ! ! (i+1, i.toString))! ! for {! a <- countState! b <- countState! } yield (a + b).toUpperCase! !
  10. StateT time
 Tuple is inside the Context ! ! case

    class SStateT[F[_], A, B](f: (A => F[(A, B)]))! !
  11. StateT time Our tuple is inside the context ! case

    class SStateT[F[_], A, B](f: (A => F[(A, B)])) {! ! def flatMap[C](fc: B => SStateT[F, A, C])! ! ! ! ! (implicit M: Monad[F]) = SStateT[F, A, C](a => {! f(a).flatMap(aa => aa match {! //returns the F[(A,C)]! ! ! ! ! ! ! case (aaa, b) => fc(b).f(aaa)! ! })! ! ! ! })! ! }!
  12. StateT flatMap Use the M instance to flatMap ! case

    class SStateT[F[_], A, B](f: (A => F[(A, B)])) {! ! def flatMap[C](fc: B => SStateT[F, A, C])! ! ! ! ! (implicit M: Monad[F]) = SStateT[F, A, C](a => {! f(a).flatMap(aa => aa match {! //returns the F[(A,C)]! ! ! ! ! ! ! case (aaa, b) => fc(b).f(aaa)! ! })! ! ! ! })! ! }!
  13. StateT flatMap Use the M instance to flatMap ! case

    class SStateT[F[_], A, B](f: (A => F[(A, B)])) {! ! def flatMap[C](fc: B => SStateT[F, A, C])! ! ! ! ! (implicit M: Monad[F]) = SStateT[F, A, C](a => {! f(a).flatMap(aa => aa match {! //returns the F[(A,C)]! ! ! ! ! ! ! case (aaa, b) => fc(b).f(aaa)! ! })! ! ! ! })! ! }!
  14. StateT flatMap Use the M instance to flatMap ! case

    class SStateT[F[_], A, B](f: (A => F[(A, B)])) {! ! def flatMap[C](fc: B => SStateT[F, A, C])! ! ! ! ! (implicit M: Monad[F]) = SStateT[F, A, C](a => {! f(a).flatMap(aa => aa match {! //returns the F[(A,C)]! ! ! ! ! ! ! case (aaa, b) => fc(b).f(aaa)! ! })! ! ! ! })! ! }!
  15. StateT Example: Counter ! ! def countState = SStateT[Future, Int,

    String]( (i: Int) => Future { (i+1, i.toString) }) ! for { a <- countState b <- countState } yield (a + b).toUpperCase
  16. An API with StateT ! case class CachedState(map: IMap[Int, Item])!

    ! def viewItem(i: Int): StateT[Task, CachedState, Item] = ???! ! def lockItem(i: Int): StateT[Task, CachedState, Unit] = ???! ! def updateItemName(id: Int, name: String): ! StateT[Task, CachedState, Item] = ???! ! def commitChange(): StateT[Task, CachedState, Unit] = ???! !
  17. ! def viewItem(i: Int): StateT[Task, CachedState, Item] = ! !

    ! ! ! ! ! StateT((cis: CachedState) => Task {! cis.map.lookup(i) match {! case Some(item) => ! ! ! ! ! ! ! ! (cis, item)! ! case None => ! ! val item = viewItemUnsafe(i)! val nmap = cis.map + (i -> item)! (CachedState(nmap), item)! }! })
  18. Usage of our API for {! a <- viewItem(4)! _

    <- lockItem(4)! _ <- updateItemName(4, "asdf")! } yield (a)!
  19. Sadly this compiles! ! for {! a <- viewItem(4)! _

    <- updateItemName(4, "asdf")! _ <- lockItem(4)! } yield (a)! !
  20. ! Indexed StateT flatMap ! ! case class ISStateT[F[_], A,

    B, C](f: (A => F[(B, C)])) { def flatMap[D, E](fc: C => ISStateT[F, B, D, E])(implicit M: Monad[F]): ISStateT[F, A, D, E] = ISStateT(a => { f(a).flatMap(t => t match { case (b, c) => fc(c).f(b) }) }) } ! ! !
  21. ! Indexed StateT flatMap ! ! case class ISStateT[F[_], A,

    B, C](f: (A => F[(B, C)])) { def flatMap[D, E](fc: C => ISStateT[F, B, D, E])(implicit M: Monad[F]): ISStateT[F, A, D, E] = ISStateT(a => { f(a).flatMap(t => t match { case (b, c) => fc(c).f(b) }) }) } ! ! !
  22. ! Indexed StateT flatMap ! ! case class ISStateT[F[_], A,

    B, C](f: (A => F[(B, C)])) { def flatMap[D, E](fc: C => ISStateT[F, B, D, E])(implicit M: Monad[F]): ISStateT[F, A, D, E] = ISStateT(a => { f(a).flatMap(t => t match { case (b, c) => fc(c).f(b) }) }) } ! ! !
  23. ! Indexed StateT flatMap ! ! case class ISStateT[F[_], A,

    B, C](f: (A => F[(B, C)])) { def flatMap[D, E](fc: C => ISStateT[F, B, D, E])(implicit M: Monad[F]): ISStateT[F, A, D, E] = ISStateT(a => { f(a).flatMap(t => t match { case (b, c) => fc(c).f(b) }) }) } ! ! !
  24. Let’s model more ‘states’ ! type Item = String! !

    case class CacheState(map: IMap[Int, Item])! ! class LockedState(map: IMap[Int, Item]) extends CacheState(map)! ! class UpdatedState(map: IMap[Int, Item]) extends LockedState(map)
  25. Our new API ! ! def viewItem(i: Int): IndexedStateT[Task, CacheState,

    CacheState, Item] = ???! ! def lockItem(i: Int): IndexedStateT[Task, CacheState, LockedState, Unit] = ???! ! def updateItemName(id: Int, item: Item): IndexedStateT[Task, LockedState, UpdatedState, Item] = ???! ! def commitChange(): IndexedStateT[Task, UpdatedState, CacheState, Unit] = ???! !
  26. Now… for {! a <- viewItem(4)! _ <- updateItemName(4, "asdf")!

    _ <- lockItem(4)! } yield (a)! ! Has the following error:! ! found : IndexedStateT[Task,cmd5.LockedState,LockedState, Item]! (which expands to) IndexedStateT[Task, cmd5.LockedState,LockedState,String]! required: IndexedStateT[Task,cmd4.CachedState,?,?]! _ <- updateItemName(4, “asdf")! ! We need a regular CachedState, not a Locked State!
 View Item doesn’t return a LockedItem
  27. Explaining the error
 
 found : IndexedStateT[Task,cmd5.LockedState,LockedState, Item] (which expands

    to) IndexedStateT[Task, cmd5.LockedState,LockedState,String]! required: IndexedStateT[Task,cmd4.CachedState,?,?]! _ <- updateItemName(4, “asdf")
 
 We can’t pass in a CacheState to something expecting a LockedState, so the types don’t work out!