Skip to content

Instantly share code, notes, and snippets.

@debasishg
Forked from lamdor/build.sbt
Created July 28, 2017 19:28
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 debasishg/6036c9f7b339fb813d379ee5e1026c50 to your computer and use it in GitHub Desktop.
Save debasishg/6036c9f7b339fb813d379ee5e1026c50 to your computer and use it in GitHub Desktop.
Playing around with tagless final style and Eff (from https://github.com/edmundnoble/final-tagless-typelevel-summit)
scalaVersion := "2.11.8"
scalaOrganization := "org.typelevel"
libraryDependencies ++= Seq(
"org.typelevel" %% "cats" % "0.9.0",
"org.atnos" %% "eff" % "4.0.0"
)
addCompilerPlugin("org.spire-math" %% "kind-projector" % "0.9.3")
scalacOptions ++= Seq(
"-deprecation",
"-encoding", "UTF-8", // yes, this is 2 args
"-feature",
"-language:existentials",
"-language:higherKinds",
"-language:implicitConversions",
"-unchecked",
"-Xlint",
"-Yno-adapted-args",
"-Ywarn-dead-code", // N.B. doesn't work well with the ??? hole
"-Ywarn-numeric-widen",
"-Ywarn-value-discard",
"-Xfuture",
"-Ypartial-unification"
)
// from https://github.com/edmundnoble/final-tagless-typelevel-summit
object tagless {
import cats.{Applicative, Monad}
import cats.data.{State, Writer, WriterT}
import cats.instances.vector._
import cats.syntax.all._
import org.atnos.eff._
import org.atnos.eff.{state, writer, either}
import org.atnos.eff.syntax.all._
trait StorageAlg[F[_]] {
def get(key: String): F[Option[String]]
def put(key: String, data: String): F[Unit]
def remove(key: String): F[Unit]
def putIfAbsent(key: String, data: String)(implicit M: Monad[F]): F[Unit] = {
get(key) flatMap { currentData =>
if (currentData.isDefined) {
().pure[F]
} else {
put(key, data)
}
}
}
}
final class MapStorageAlg extends StorageAlg[State[Map[String, String], ?]] {
def get(key: String): State[Map[String, String], Option[String]] =
State.inspect(_.get(key))
def put(key: String, data: String): State[Map[String, String], Unit] =
State.modify(_ + (key -> data))
def remove(key: String): State[Map[String, String], Unit] =
State.modify(_ - key)
}
sealed trait ProgramError
case object NotFound extends ProgramError
def program[F[_]: Monad](implicit storage: StorageAlg[F]): F[Either[ProgramError, String]] = {
import storage._
for {
_ <- putIfAbsent("name", "Luke")
me <- get("name")
_ <- remove("name")
} yield me.toRight(NotFound)
}
def useMapStorage(): Unit = {
implicit val storage = new MapStorageAlg
val f = program[State[Map[String, String], ?]]
val result: Either[ProgramError, String] = f.runA(Map.empty).value
println(s"result = ${result}")
}
final class LogRemovedKeys[F[_] : Applicative](storage: StorageAlg[F])
extends StorageAlg[WriterT[F, Vector[String], ?]] {
def get(key: String): WriterT[F, Vector[String], Option[String]] =
WriterT.lift(storage.get(key))
def put(key: String, data: String): WriterT[F, Vector[String], Unit] =
WriterT.lift(storage.put(key, data))
def remove(key: String): WriterT[F, Vector[String], Unit] =
WriterT.putT(storage.remove(key))(Vector(key))
}
def useLogRemovedKeysMapStorage(): Unit = {
implicit val storage = new LogRemovedKeys[State[Map[String, String], ?]](new MapStorageAlg)
val f = program[WriterT[State[Map[String, String], ?], Vector[String], ?]]
val (deleteLog: Vector[String], result: Either[ProgramError, String]) = f.run.runA(Map.empty).value
println(s"result = ${result}")
println(s"deleteLog = ${deleteLog}")
}
final class MapStorageAlgEff[R](implicit member: State[Map[String, String], ?] |= R)
extends StorageAlg[Eff[R, ?]] {
def get(key: String): Eff[R, Option[String]] =
state.get.map(_.get(key))
def put(key: String, data: String): Eff[R, Unit] =
state.get.flatMap { m => state.put(m + (key -> data)) }
def remove(key: String): Eff[R, Unit] =
state.get.flatMap { m => state.put(m - key) }
}
final class LogRemovedKeysEff[R](storage: StorageAlg[Eff[R, ?]])
(implicit member: Writer[String, ?] |= R)
extends StorageAlg[Eff[R, ?]] {
def get(key: String): Eff[R, Option[String]] =
storage.get(key)
def put(key: String, data: String): Eff[R, Unit] =
storage.put(key, data)
def remove(key: String): Eff[R, Unit] =
writer.tell[R, String](key) >> storage.remove(key)
}
def useMapStorageEff(): Unit = {
type S = Fx.fx1[State[Map[String, String], ?]]
implicit val storage = new MapStorageAlgEff[S]
val f: Eff[S, Either[ProgramError, String]] = program[Eff[S, ?]]
val result: Either[ProgramError, String] = f.evalState(Map.empty[String, String]).run
println(s"result = ${result}")
}
def useLogRemovedKeysMapStorageEff(): Unit = {
type S = Fx.fx2[State[Map[String, String], ?],
Writer[String, ?]]
implicit val storage = new LogRemovedKeysEff[S](new MapStorageAlgEff[S])
val f: Eff[S, Either[ProgramError, String]] = program[Eff[S, ?]]
val (result: Either[ProgramError, String], deleteLog: List[String]) =
f.runWriter.evalState(Map.empty[String, String]).run
println(s"result = ${result}")
println(s"deleteLog = ${deleteLog}")
}
def programEff[RS](storage: StorageAlg[Eff[RS, ?]])
(implicit member: Either[ProgramError, ?] |= RS): Eff[RS, String] = {
import storage._
for {
_ <- putIfAbsent("name", "Luke")
me <- get("name")
_ <- remove("name")
a <- either.optionEither[RS, ProgramError, String](me, NotFound)
} yield a
}
def useAllTheEff(): Unit = {
type S = Fx.fx3[State[Map[String, String], ?],
Writer[String, ?],
Either[ProgramError, ?]]
val storage = new LogRemovedKeysEff[S](new MapStorageAlgEff[S])
val f: Eff[S, String] = programEff[S](storage)
val (result: Either[ProgramError, String], deleteLog: List[String]) =
f.runEither.runWriter.evalState(Map.empty[String, String]).run
println(s"result = ${result}")
println(s"deleteLog = ${deleteLog}")
}
// following is from composing Free monads
// http://typelevel.org/cats/datatypes/freemonad.html
trait InteractAlg[F[_]] {
def ask(prompt: String): F[String]
def tell(msg: String): F[Unit]
}
trait CatsRepositoryAlg[F[_]] {
def addCat(a: String): F[Unit]
def getAllCats(): F[List[String]]
}
def catsProgram[F[_]: Monad](implicit interactAlg: InteractAlg[F],
catsRepoAlg: CatsRepositoryAlg[F]): F[Unit] = {
import interactAlg._, catsRepoAlg._
for {
cat <- ask("What's the kitty's name?")
_ <- addCat(cat)
cats <- getAllCats
_ <- tell(cats.toString)
} yield ()
}
import org.atnos.eff.eval._
final class ConsoleInteractAlg[R : _eval] extends InteractAlg[Eff[R, ?]] {
def ask(prompt: String): Eff[R, String] =
tell(prompt) >> delay(scala.io.StdIn.readLine())
def tell(msg: String): Eff[R, Unit] =
delay(println(msg))
}
final class StateCatsRepositoryStAlg[R](implicit member: State[List[String], ?] |= R) extends CatsRepositoryAlg[Eff[R, ?]] {
def addCat(a: String): Eff[R, Unit] =
state.get.flatMap { cats => state.put(a +: cats) }
def getAllCats(): Eff[R, List[String]] = state.get
}
import cats.Eval
import cats.instances.list._
def runCatsProgram(): Unit = {
type S = Fx.fx2[Eval, State[List[String], ?]]
implicit val interactAlg = new ConsoleInteractAlg[S]
implicit val catsRepoAlg = new StateCatsRepositoryStAlg[S]
val effects: Eff[S, Unit] = catsProgram[Eff[S, ?]]
effects.evalStateZero[List[String]].runEval.run
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment