Update

Implementation

trait UpdateAction[P,S] {
  def apply(p: P, s: S)(implicit mp: Monoid[P]): S
}

case class Update[S,P:Monoid,A](run: S => (P,A))(implicit ua: UpdateAction[P,S]) {

  import Monoid.MonoidOps

  def map[B](f: A => B): Update[S,P,B] =
    Update { s =>
      val (p, a) = run(s)
      val b = f(a)
      (p, b)
    }

  def flatMap[B](f: A => Update[S,P,B]): Update[S,P,B] =
    Update { s1 =>
      val (p1, a) = run(s1)
      val s2 = implicitly[UpdateAction[P,S]].apply(p1, s1)
      val (p2, b) = f(a).run(s2)
      (p1 <#> p2, b)
    }
}

object Update {

  def apply[S,P:Monoid,A](a: A)(implicit ua: UpdateAction[P,S]): Update[S,P,A] =
    Update(_ => (implicitly[Monoid[P]].mempty, a))

  def putAction[S,P:Monoid](p: P)(implicit ua: UpdateAction[P,S]): Update[S,P,Unit] =
    Update(_ => (p, ()))

  def getState[S,P:Monoid](implicit ua: UpdateAction[P,S]): Update[S,P,S] =
    Update(s => (implicitly[Monoid[P]].mempty, s))

}

Dependencies

Example

implicit def listMonoid[A] =
  new Monoid[List[A]] {
    def mempty: List[A] = Nil
    def mappend(a1: List[A], a2: List[A]): List[A] = a1 ++ a2
  }

sealed trait MapAction[K,V]
case class Put[K,V](k: K, v: V) extends MapAction[K,V]
case class Delete[K,V](k: K) extends MapAction[K,V]

implicit def applyAction[K,V] =
  new UpdateAction[List[MapAction[K,V]], Map[K,V]] {
    def apply(p: List[MapAction[K,V]], s: Map[K,V])
             (implicit mp: Monoid[List[MapAction[K,V]]]): Map[K,V] =
      p match {
        case Nil => s
        case Put(k, v) :: tail => apply(tail, s + (k -> v))
        case Delete(k) :: tail => apply(tail, s - k)
      }
  }

type S = Map[String, Int]
type P = List[MapAction[String, Int]]
type U[A] = Update[S,P,A]

val u: U[S] =
  for {

    _ <- Update.putAction[S,P](List(Put("x", 6)))
    _ <- Update.putAction[S,P](List(Put("y", 7)))

    x <- Update.getState[S,P] map { s: S => s.getOrElse("x", 0) }
    y <- Update.getState[S,P] map { s: S => s.getOrElse("y", 0) }

    _ <- Update.putAction[S,P](List(Delete("x")))
    _ <- Update.putAction[S,P](List(Delete("y")))

    _ <- Update.putAction[S,P](List(Put("z", x * y)))
    z <- Update.getState[S,P] map { s: S => s.getOrElse("z", 0) }

    s <- Update.getState[S,P]

  } yield s

println(u.run(Map.empty)._2) // Map(z -> 42)

Demo

This file is literate Scala, and can be run using Codedown:

$ curl \
  https://earldouglas.com/type-classes/monoid.md \
  https://earldouglas.com/type-classes/semigroup.md \
  https://earldouglas.com/type-classes/update.md |
  codedown scala | xargs -0 scala -nc -e
Map(z -> 42)