Beyond Mere Actors

32
Beyond Mere Actors Concurrent Functional Programming with Scalaz Rúnar Bjarnason

description

Boston Area Scala Enthusiasts fourth meeting held on Feb 2, 2010.Talk delivered by Rúnar Bjarnason.

Transcript of Beyond Mere Actors

Page 1: Beyond Mere Actors

Beyond Mere Actors

Concurrent Functional Programming with Scalaz

Rúnar Bjarnason

Page 2: Beyond Mere Actors

Traditional Java Concurrency

Manual creation of threadsManual synchronisationOne thread per processProcesses communicate by shared mutable state

Page 3: Beyond Mere Actors

Traditional Java Concurrency

Manual creation of threadsManual synchronisationOne thread per processProcesses communicate by shared mutable stateProblem: Threads do not compose

Page 4: Beyond Mere Actors

java.util.concurrent

Since JDK 5 Includes useful building blocks for making higher-level abstractions.Atomic referencesCountdown latchesExecutorServiceCallableFuture

Page 5: Beyond Mere Actors

Futures

Provided by java.util.concurrent.Future

Future[A] represents a computation of type A, executing concurrently.

Future.get: A

Page 6: Beyond Mere Actors

Futures

ExecutorService is a means of turning Callable[A] into Future[A]implicit def toCallable[A](a: => A) = new Callable[A] { def call = a }implicit val s = Executors.newCachedThreadPool

e1 and e2 are evaluated concurrentlyval x = s submit { e1 }val y = e2 Futures can participate in expressions:val z = f(x.get, y)

No mention of threads in this code. No shared state.

Page 7: Beyond Mere Actors

Futures

There's a serious problem. How would we implement this function?

def fmap[A,B](fu: Future[A], f: A => B) (implicit s: ExecutorService):Future[B] =

Page 8: Beyond Mere Actors

Futures

We have to call Future.get, blocking the thread.

def fmap[A,B](fu: Future[A], f: A => B) (implicit s: ExecutorService):Future[B] = s submit f(fu.get) This is a barrier to composition. Futures cannot be composed without blocking a thread.

Page 9: Beyond Mere Actors

scalaz.concurrent

Strategy - Abstracts over ways of evaluating expressions concurrently. Actor - Light-weight thread-like process, communicates by asynchronous messaging. Promise - Compose concurrent functions.

Page 10: Beyond Mere Actors

Parallel Strategies

ExecutorService: Callable[A] => Future[A]

Callable[A] ~= Function0[A]Future[A] ~= Function0[A]Also written: () => A

Strategy[A] ~= Function0[A] => Function0[A]Also written: (() => A) => () => A

Turns a lazy expression of type A into an expression of the same type.

Page 11: Beyond Mere Actors

scalaz.concurrent.Strategy

Separates the concerns of parallelism and algorithm.Captures some threading pattern and isolates the rest of your code from threading. Executor - wraps the expression in a Callable, turns it into a Future via an implicit ExecutorService.

Naive - Starts a new thread for each expression. Sequential - Evaluates each expression in the current thread (no concurrency). Identity - Performs no evaluation at all.

Page 12: Beyond Mere Actors

Parallel Strategies

You can write your own Strategies. Some (crazy) ideas:

Delegate to the fork/join scheduler.Run in the Swing thread.Call a remote machine.Ask a human, or produce a random result.

Page 13: Beyond Mere Actors

Actors

Provide asynchronous communication among processes.

Processes receive messages on a queue.

Messages can be enqueued with no waiting.

An actor is always either suspended (waiting for messages) or working (acting on a message).

An actor processes messages in some (but any) order.

Page 14: Beyond Mere Actors

scalaz.concurrent.Actor

These are not scala.actors. Differences:Simpler. Scalaz Actors are distilled to the essentials.Messages are typed.

Actor is sealed, and instantiated by supplying: type Aeffect: A => Unit (implicit) strategy: Strategy[Unit](Optional) Error Handler: Throwable => Unit

Strategy + Effect = Actor

Page 15: Beyond Mere Actors

Actor Example

Page 16: Beyond Mere Actors

Actor: Contravariant Cofunctor

An actor can be composed with a function: x.comap(f) Comap has this type: comap: (B => A) => Actor[A] => Actor[B] Returns a new actor that applies f to its messages and sends the result to actor x. x comap f is equivalent to x compose f, but results in an Actor, as opposed to a function.

Page 17: Beyond Mere Actors

Problems with Actors

Page 18: Beyond Mere Actors

Problems with Actors

You have to think about state and process communication.An actor can (must?) expose its state.It's all about side-effects!

Side-effects absolutely do not compose.You cannot compose Actors with each other.

Actor[A] ~= (A => Unit)

There's not a lot you can do with Unit.

Page 19: Beyond Mere Actors

scalaz.concurrent.Promise

Similar to Future, but non-blocking.

Implements map and flatMap without calling get.

Page 20: Beyond Mere Actors

scalaz.concurrent.Promise

Constructed by giving an expression to promise: lazy val e:String = {Thread sleep 5000; "Hi."} val p: Promise[String] = promise(e) Takes an implicit Strategy[Unit]. The expression is evaluated concurrently by the Strategy.Think of this as forking a process. The result is available later by calling p.get. This blocks the current thread. But we never have to call it!

Page 21: Beyond Mere Actors

On Time-Travel

Promised values are available in the future.

What does it mean to get a value out of the future?Time-travel into the future is easy. Just wait.But we don't have to go into the future.We can give our future-selves instructions.

Instead of getting values out of the future, we send computations into the future.

Page 22: Beyond Mere Actors

Lifting a function into the future

Consider: promise(e).map(f)

map has the following type:(A => B) => Promise[A] => Promise[B] We take an ordinary function and turn it into a function that operates on Promises. It's saying: Evaluate e concurrently, applying f to the result when it's ready. Returns a Promise of the final result.

Page 23: Beyond Mere Actors

Composing concurrent functions

A concurrent function is of the type A => Promise[B] Syntax sugar to make any function a concurrent function:val g = f.promisepromise(f: A => B) = (a:A) => promise(f(a)) We can bind the arguments of concurrent functions to promised values, using flatMap:promise(e).flatMap(g) flatMap has this type:(A => Promise[B]) => Promise[A] => Promise[B]

Page 24: Beyond Mere Actors

Composing concurrent functions

We can compose concurrent functions with each other too. If f: A => Promise[B] and g: B => Promise[C] then (f >=> g): A => Promise[C]

(f >=> g)(x) is equivalent to f(x) flatMap g

Page 25: Beyond Mere Actors

Joining Promises

join[A]: Promise[Promise[A]] => Promise[A]

(promise { promise { expression } }).join Join removes the "inner brackets". A process that forks other processes can join with them later, without synchronizing or blocking.A process whose result depends on child processes is still just a single Promise, and thus can run in a single thread. Therefore, pure promises cannot deadlock or starve.

Page 26: Beyond Mere Actors

Promises - Example

Page 27: Beyond Mere Actors

But wait, there's more!

Parallel counterparts of map, flatMap, and zipWith:parMap, parFlatMap, and parZipWith x.parMap(f) Where x can be a List, Stream, Function, Option, Promise, etc.Scalaz provides parMap on any Functor. parZipWith for parallel zipping of any Applicative Functor. parFlatMap is provided for any Monad.

Page 28: Beyond Mere Actors

Advanced Topics

If you understand Promise, then you understand monads.

Page 29: Beyond Mere Actors

Advanced Topics

Functor is simply this interface:

trait Functor[F[_]] { def fmap[A, B](r: F[A], f: A => B): F[B]}

Functors are "mappable". Any implementation of this interface is a functor. Here's the Promise functor:new Functor[Promise] { def fmap[A, B](t: Promise[A], f: A => B) = t.flatMap(a => promise(f(a)))}

Page 30: Beyond Mere Actors

Advanced Topics

Monad is simply this interface:trait Monad[M[_]] extends Functor[M] { fork[A](a: A): M[A] join[A](a: M[M[A]]): M[A]}

Monads are fork/map/joinable. Any implementation of this interface is a monad. Here's the Promise monad:new Monad[Promise] { def fork[A](a: A) = promise(a) def join[A](a: Promise[Promise[A]]) = a.flatMap(Functions.identity)}

Page 31: Beyond Mere Actors

Welcome to Scalaz

Scalaz is a general-purpose library for higher-order programming. There's a lot here. Go play around, and ask questions on the Scalaz Google Group. For more information:http://code.google.com/p/scalaz

Documentation is lacking, but we're working on that. A release of Scalaz 5.0 will coincide with a release of Scala 2.8.

Page 32: Beyond Mere Actors

Questions?