Skip to content

Instantly share code, notes, and snippets.

@Fristi
Last active April 25, 2019 13:12
Show Gist options
  • Save Fristi/562301c62252887fdffd to your computer and use it in GitHub Desktop.
Save Fristi/562301c62252887fdffd to your computer and use it in GitHub Desktop.
Free Monad operating on Akka actors
package nl.mdj.fpinscala
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import akka.pattern.ask
import akka.util.Timeout
import nl.mdj.fpinscala.KeyValueStore.{KvS, ListKeys}
import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Await, Future}
import scalaz.std.list._
import scalaz.std.scalaFuture
import scalaz.syntax.traverse._
import scalaz.{Coyoneda, Free, ~>}
object FreeActorMain extends App {
implicit val executionContext = scala.concurrent.ExecutionContext.global
val program = {
val valuesToPut = for(i <- 1 to 100000) yield s"entry.$i" -> s"value.$i"
def valueForKey(key: String) = for {
value <- KeyValueStore.get(key)
} yield key -> value
for {
_ <- valuesToPut.toList.traverseU { case (k,v) => KeyValueStore.put(k,v) }
keys <- KeyValueStore.listKeys
kvps <- keys.traverseU(valueForKey)//.map(valueForKey).sequenceU
} yield kvps
}
def timed[A](ft: => Future[A])(implicit ec: ExecutionContext): Future[A] = {
val start = System.nanoTime()
println(s"start: $start")
ft.onComplete { _ =>
val end = System.nanoTime()
val elapsed = end - start
println(s"end: $end")
println(s"Elapsed in $elapsed us")
}
ft
}
val system = ActorSystem()
val actor = system.actorOf(KeyValueStore.props)
val nat = KeyValueStore.interperter(actor)(Timeout(3.second))
val result = timed {
Free.runFC(program)(nat)(scalaFuture.futureInstance)
}
Await.result(result, 9.second)
println("Done")
}
class KeyValueStore extends Actor {
var map = Map.empty[String, Any]
override def receive: Receive = {
case u:KeyValueStore.Get => sender ! map.get(u.key)
case u:KeyValueStore.Exists => sender ! map.contains(u.key)
case ListKeys => sender ! map.keys.toList
case u:KeyValueStore.Put =>
if(!map.contains(u.key)) {
map += u.key -> u.value
sender ! true
} else {
sender ! false
}
}
}
object KeyValueStore {
sealed trait KeyValue[A]
case class Put(key: String, value: String) extends KeyValue[Boolean]
case class Exists(key: String) extends KeyValue[Boolean]
case class Get(key: String) extends KeyValue[Option[String]]
case object ListKeys extends KeyValue[List[String]]
type KvS[A] = Coyoneda[KeyValue, A]
def put[A](key: String, value: String): Free[KvS, Boolean] = Free.liftFC(Put(key, value))
def get[A](key: String): Free[KvS, Option[String]] = Free.liftFC(Get(key))
def exists[A](key: String): Free[KvS, Boolean] = Free.liftFC(Exists(key))
def listKeys: Free[KvS, List[String]] = Free.liftFC(ListKeys)
def props = Props(new KeyValueStore())
def interperter(actorRef: ActorRef)(implicit timeout: Timeout) = new (KeyValue ~> Future) {
override def apply[A](fa: KeyValue[A]): Future[A] =
actorRef.ask(fa).asInstanceOf[Future[A]]
}
}
@1ambda
Copy link

1ambda commented Aug 30, 2015

Nice examples!
I have a question. Should all free monad interpreters return something?
What if i want to use Actor.tell instead of Actor.ask?

for example,

  def createInterpreter(actor: ActorRef) = new (GithubRequest ~> Unit) {
    override def apply[A](fa: GithubRequest[A]): Unit =
      actor ! fa 
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment