In this presentation, John A. De Goes looks at several concurrency and scalability problems similar to the ones all programmers have to face, and shows how purely functional solutions written using Scalaz 8 are shorter, faster, easier to test, and easier to understand than competing solutions written using Akka actors. Discover how functional programming can be your secret superpower when it comes to quickly building bullet-proof business applications!
4. Ultimate Question of Software Development
How can we bend light so we
can reactively build reactive
microsystems that react to
reacting reactivity???
5. Answer to the Ultimate Question of Software Development
PartialFunction[Any, Unit]
Input Message
AKA “Actor”
6. Killer Use Cases for Actors
1. Parallelism 2. Concurrent State 3. Distributed Compute3. Persistence
7. 1. Parallelism
sealed trait Message
case class Chunk(v: Array[Byte])
class ChunkActor extends Actor {
def receive = {
case Chunk(v) => sender ! encrypt(v)
}
}
val chunkActors =
context.actorOf(Props[ChunkActor].
withRouter(RoundRobinPool(4)), name =
"ChunkActors")
…
chunks.foreach (chunk => chunkActors forward
(Chunk(chunk)))
IO.concurrently(chunks.map(encrypt(_)))
8. Killer Use Cases for Actors
1. Parallelism 2. Concurrent State 3. Distributed Compute3. Persistence
9. 2. Concurrent State
sealed trait Message
case object Get extends Message
case class Inc(value: Int) extends Message
class Counter extends Actor {
var counter: Int = 0
def receive = {
case Get => sender ! counter
case Inc(v) =>
counter += v
sender ! counter
}
}
...
val system = ActorSystem("counter")
val counter = system.actorOf(Props[Counter], "counter")
counter ! Inc(20)
val counter = IORef(0)
...
counter.modify(_ + 20)
10. Killer Use Cases for Actors
1. Parallelism 2. Concurrent State 3. Distributed Compute3. Persistence
11. 3. Persistence
class MyProcessor extends Processor {
def receive = {
case Persistent(payload, sequence) =>
doWork(payload, sequence)
case other =>
}
}
...
val processor =
actorOf(Props[MyProcessor],
name = "myProcessor")
processor ! Persistent("foo")
processor ! "bar"
val processor = payload =>
for {
sequence <- counter.modify(_ + 1)
_ <- persist(payload)
v <- doWork(payload, sequence)
} yield v
12. Killer Use Cases for Actors
1. Parallelism 2. Concurrent State 3. Distributed Compute3. Persistence
13. 4. Distributed Compute
class SimpleClusterListener extends Actor with ActorLogging {
val cluster = Cluster(context.system)
// subscribe to cluster changes, re-subscribe when restart
override def preStart(): Unit = {
cluster.subscribe(self, initialStateMode = InitialStateAsEvents,
classOf[MemberEvent], classOf[UnreachableMember])
}
override def postStop(): Unit = cluster.unsubscribe(self)
def receive = {
case MemberUp(member) =>
log.info("Member is Up: {}", member.address)
case UnreachableMember(member) =>
log.info("Member detected as unreachable: {}", member)
case MemberRemoved(member, previousStatus) =>
log.info(
"Member is Removed: {} after {}",
member.address, previousStatus)
case _: MemberEvent => // ignore
}
}
¯_(ツ)_/¯
22. Next-Generation Purely Functional Actor Design
def increment(n: Int): IO[Void, Int] =
for {
counter <- IORef(0)
value <- counter.modify(_ + n)
} yield value
23. Next-Generation Purely Functional Actor Design
val makeActor: IO[Void, Int => IO[Void, Int]] =
for {
counter <- IORef(0)
actor = (n: Int) => counter.modify(_ + n)
} yield actor
24. Next-Generation Purely Functional Actor Design
type Actor[E, I, O] = I => IO[E, O]
val makeActor: IO[Void, Actor[Void, Int, Int]] =
for {
counter <- IORef(0)
actor = (n: Int) => counter.modify(_ + n)
} yield actor
implicit class ActorSyntax[E, I, O](actor: Actor[E, I, O]) {
def ! (i: I): IO[E, O] = actor(i)
}
...
for {
v <- actor ! 20
} yield v
25. Next-Generation Purely Functional Actor Design
val makeActor: IO[Void, Actor[Void, Int, Int]] =
for {
counter <- IORef(0)
queue <- AsyncQueue.make[(Int, Promise[Void, Int])]
worker <- queue.take.flatMap(t =>
counter.modify(_ + t._1).flatMap(t._2.complete)).forever.fork
actor = (n: Int) =>
for {
promise <- Promise.make[Void, Int]
_ <- queue.offer((n, promise))
value <- promise.get
} yield value
} yield actor
26. Next-Generation Purely Functional Actor Design
type Actor[E, I, O] = I => IO[E, O]
def persistIn[E, I: EncodeJson, O](actor: Actor[E, I, O]): Actor[E, I, O]
def persistOut[E, I, O: EncodeJson](actor: Actor[E, I, O]): Actor[E, I, O]
def compose[E, I, O, U](l: Actor[E, I, O], r: Actor[E, O, U]): Actor[E, I, U]
...
30. Top Reasons to Choose Actors*
1. Your Code Is Way
Too Fast
2. Your Code Is Too
Composable
3. Your Code Is Too Easy to
Understand
*Actors are sometimes useful, like assembly language. But we can often do better.
31. Scalaz 8 vs Akka Actors
THANK YOU TO SCALAR & SOFTWAREMILL!
John A. De Goes
@jdegoes - http://degoes.net