beyond mere actors

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

Upload: boston-area-scala-enthusiasts

Post on 07-May-2015

4.201 views

Category:

Technology


0 download

DESCRIPTION

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

TRANSCRIPT

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?