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] =
{ s =>
Update val (p, a) = run(s)
val b = f(a)
(p, b)
}
def flatMap[B](f: A => Update[S,P,B]): Update[S,P,B] =
{ s1 =>
Update 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))
}
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] =
match {
p 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)))
_
<- Update.getState[S,P] map { s: S => s.getOrElse("x", 0) }
x <- Update.getState[S,P] map { s: S => s.getOrElse("y", 0) }
y
<- Update.putAction[S,P](List(Delete("x")))
_ <- Update.putAction[S,P](List(Delete("y")))
_
<- Update.putAction[S,P](List(Put("z", x * y)))
_ <- Update.getState[S,P] map { s: S => s.getOrElse("z", 0) }
z
<- Update.getState[S,P]
s
} yield s
println(u.run(Map.empty)._2) // Map(z -> 42)
This file is literate Scala, and can be run using Codedown:
$ curl \
https://earldouglas.com/posts/type-classes/monoid.md \
https://earldouglas.com/posts/type-classes/semigroup.md \
https://earldouglas.com/posts/type-classes/update.md |
codedown scala | xargs -0 scala -nc -e
Map(z -> 42)