Skip to content

Instantly share code, notes, and snippets.

@trbngr
Last active January 11, 2017 02:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save trbngr/b30468dd54ce4473c62985b2c98a3367 to your computer and use it in GitHub Desktop.
Save trbngr/b30468dd54ce4473c62985b2c98a3367 to your computer and use it in GitHub Desktop.
name := "eff-study"
version := "1.0"
scalaVersion := "2.12.1"
scalacOptions ++= Seq(
"-Ypartial-unification",
"-language:postfixOps",
"-language:implicitConversions",
"-language:higherKinds"
)
libraryDependencies += "org.atnos" %% "eff" % "2.2.0"
addCompilerPlugin("org.spire-math" %% "kind-projector" % "0.9.3")
import cats.Traverse
import cats.data._
import cats.implicits._
import org.atnos.eff._
import org.atnos.eff.all._
import org.atnos.eff.interpret._
import org.atnos.eff.syntax.all._
import scala.collection.mutable
object main extends App {
sealed trait KVStore[+A]
case class Put[T](key: String, value: T) extends KVStore[Unit]
case class Get[T](key: String) extends KVStore[Option[T]]
case class Delete(key: String) extends KVStore[Unit]
// T |= R is an alias for MemberIn[T, R]
// stating that effects of type T[_] can be injected in the effect stack R
// It is also equivalent to MemberIn[KVStore, R]
type _kvstore[R] = KVStore |= R
/** put returns nothing (i.e. Unit) */
def put[T, R :_kvstore](key: String, value: T): Eff[R, Unit] =
Eff.send[KVStore, R, Unit](Put(key, value))
/** get returns a T value if the key exists */
def get[T, R :_kvstore](key: String): Eff[R, Option[T]] =
Eff.send[KVStore, R, Option[T]](Get(key))
/** delete returns nothing (i.e. Unit) */
def delete[T, R :_kvstore](key: String): Eff[R, Unit] =
Eff.send(Delete(key))
/** update composes get and set, and returns nothing. */
def update[T, R :_kvstore](key: String, f: T => T): Eff[R, Unit] =
get[T, R](key).map(_.map(f)).void
/**
* Unsafe interpreter for KVStore effects
*
* the program will crash if a type is incorrectly specified.
*
* The interpreter requires the KVStore effect to be a Member of R (with <=)
* Meaning that we can statically know the resulting type once we have removed
* KVStore from R, and this type is m.Out.
*
* The interpreter uses the `interpretUnsafe` method from `org.atnos.eff.Interpreter` to implement a
* stack-safe interpretation of effects as a side-effect.
*
* `interpretUnsafe` needs the definition of a side-effect where
* we get each `KVStore[X]` effect, run side-effects and return a value `X`.
*
* The resulting effect stack is m.Out which is R without the KVStore effects
*
*/
def runKVStoreUnsafe[R, A](effects: Eff[R, A])(implicit m: KVStore <= R): Eff[m.Out, A] = {
// a very simple (and imprecise) key-value store
val kvs = mutable.Map.empty[String, Any]
val sideEffect = new SideEffect[KVStore] {
def apply[X](kv: KVStore[X]): X =
kv match {
case Put(key, value) =>
println(s"put($key, $value)")
kvs.put(key, value)
().asInstanceOf[X]
case Get(key) =>
println(s"get($key)")
kvs.get(key).asInstanceOf[X]
case Delete(key) =>
println(s"delete($key)")
kvs.remove(key)
().asInstanceOf[X]
}
def applicative[X, Tr[_] : Traverse](ms: Tr[KVStore[X]]): Tr[X] =
ms.map(apply)
}
interpretUnsafe(effects)(sideEffect)(m)
}
/**
* Safe interpreter for KVStore effects
*
* It uses the following effects:
*
* - Writer to create log statements
* - State to update a key-value Map
* - Either to raise errors if the type of an object in the map is not of the expected type
*
* The resulting effect stack is U which is R without the KVStore effects
*
* Note that we just require the Throwable, Writer and State effects to
* be able to be created in the stack U
*
* This interpreter uses the org.atnos.eff.interpreter.translate method
* translating one effect of the stack to other effects in the same stack
*
*
* NOTE:
* - It is really important for type inference that the effects for U are listed after those for R!
* - There's a name conflict in the `State Monad` in the pattern match for `Get(key)`
* ;hence, remember to rename your smart constructor defined earlier to something else.
*
* Implicit member definitions will NOT be found with the following definition:
*
* def runKVStore[R, U :_throwableEither :_writerString :_stateMap, A](effects: Eff[R, A]) (
* implicit m: Member.Aux[KVStore, R, U]): Eff[U, A] = {
*
*/
type _writerString[R] = Writer[String, ?] |= R
type _stateMap[R] = State[Map[String, Any], ?] |= R
def runKVStore[R, U, A](effects: Eff[R, A])
(implicit m: Member.Aux[KVStore, R, U],
throwable:_throwableEither[U],
writer:_writerString[U],
state:_stateMap[U]): Eff[U, A] = {
translate(effects)(new Translate[KVStore, U] {
def apply[X](kv: KVStore[X]): Eff[U, X] =
kv match {
case Put(key, value) =>
for {
_ <- tell(s"put($key, $value)")
_ <- modify((map: Map[String, Any]) => map.updated(key, value))
r <- fromEither(Either.catchNonFatal(().asInstanceOf[X]))
} yield r
case Get(key) =>
for {
_ <- tell(s"get($key)")
m <- get[U, Map[String, Any]]
r <- fromEither(Either.catchNonFatal(m.get(key).asInstanceOf[X]))
} yield r
case Delete(key) =>
for {
_ <- tell(s"delete($key)")
u <- modify((map: Map[String, Any]) => map - key)
r <- fromEither(Either.catchNonFatal(().asInstanceOf[X]))
} yield r
}
})
}
// //program def
// def program[R: _kvstore]: Eff[R, Option[Int]] =
// for {
// _ ← put("wild-cats", 2)
// _ ← update[Int, R]("wild-cats", _ + 12)
// _ ← put("tame-cats", 5)
// n ← get[Int, R]("wild-cats")
// _ ← delete("tame-cats")
// } yield n
//
// println(runKVStoreUnsafe(program[Fx.fx1[KVStore]]).run)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment