├── .gitignore
├── Index.md
├── README.md
├── ReactiveCheatSheet.md
├── _config.yml
└── _layouts
└── page.html
/.gitignore:
--------------------------------------------------------------------------------
1 | _site
2 |
--------------------------------------------------------------------------------
/Index.md:
--------------------------------------------------------------------------------
1 |
Monads
2 | For-comprehensions are a 'simplified' way of writing some specific map, flatMap and filter expressions. There are certain properties of data-structures that make these conversions feasible. Monads are structures that define two operations: flatMap and unit which enjoy properties like Associativity of flatMap and simple Left and Right Unit application. These properties of Monads help simplify for-expressions involving them.
3 |
4 | Read this for more on Monads: https://github.com/sjuvekar/reactive-programming-scala/blob/master/Monads.md
5 |
6 | Functional Programming and Mutable State
7 | Reactive systems need the objects to maintain and manipulate states.
8 |
9 | Read this to see what mutable states mean in functional programming context: https://github.com/sjuvekar/reactive-programming-scala/blob/master/MutableStates.md
10 |
11 | Futures
12 |
13 | Actors and Concurrency
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is my repository containing a references and cheat sheets for Coursera's class on Reactive Programming (https://class.coursera.org/reactive-001).
2 |
--------------------------------------------------------------------------------
/ReactiveCheatSheet.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: page
3 | title: Reactive Cheat Sheet
4 | ---
5 |
6 | This cheat sheet originated from the forums. There are certainly a lot of things that can be improved! If you would like to contribute, you have two options:
7 |
8 | Click the "Edit" button on this file on GitHub:
9 | [https://github.com/sjuvekar/reactive-programming-scala/blob/master/ReactiveCheatSheet.md](https://github.com/sjuvekar/reactive-programming-scala/edit/master/ReactiveCheatSheet.md)
10 | You can submit a pull request directly from there without checking out the git repository to your local machine.
11 |
12 | Fork the repository [https://github.com/sjuvekar/reactive-programming-scala/](https://github.com/sjuvekar/reactive-programming-scala/) and check it out locally. To preview your changes, you need jekyll. Navigate to your checkout and invoke `jekyll serve --watch` (or `jekyll --auto --server` if you have an older jekyll version), then open the page `http://localhost:4000/ReactiveCheatSheet.html`.
13 |
14 |
15 | ## Partial Functions
16 |
17 | A subtype of trait `Function1` that is well defined on a subset of its domain.
18 | ```scala
19 | trait PartialFunction[-A, +R] extends Function1[-A, +R] {
20 | def apply(x: A): R
21 | def isDefinedAt(x: A): Boolean
22 | }
23 | ```
24 |
25 | Every concrete implementation of PartialFunction has the usual `apply` method along with a boolean method `isDefinedAt`.
26 |
27 | **Important:** An implementation of PartialFunction can return `true` for `isDefinedAt` but still end up throwing RuntimeException (like MatchError in pattern-matching implementation).
28 |
29 | A concise way of constructing partial functions is shown in the following example:
30 |
31 | ```scala
32 | trait Coin {}
33 | case class Gold() extends Coin {}
34 | case class Silver() extends Coin {}
35 |
36 | val pf: PartialFunction[Coin, String] = {
37 | case Gold() => "a golden coin"
38 | // no case for Silver(), because we're only interested in Gold()
39 | }
40 |
41 | println(pf.isDefinedAt(Gold())) // true
42 | println(pf.isDefinedAt(Silver())) // false
43 | println(pf(Gold())) // a golden coin
44 | println(pf(Silver())) // throws a scala.MatchError
45 | ```
46 |
47 |
48 | ## For-Comprehension and Pattern Matching
49 |
50 | A general For-Comprehension is described in Scala Cheat Sheet here: [https://github.com/lrytz/progfun-wiki/blob/gh-pages/CheatSheet.md](https://github.com/lrytz/progfun-wiki/blob/gh-pages/CheatSheet.md). One can also use Patterns inside for-expression. The simplest form of for-expression pattern looks like
51 |
52 | ```scala
53 | for { pat <- expr} yield e
54 | ```
55 |
56 | where `pat` is a pattern containing a single variable `x`. We translate the `pat <- expr` part of the expression to
57 |
58 | ```scala
59 | x <- expr withFilter {
60 | case pat => true
61 | case _ => false
62 | } map {
63 | case pat => x
64 | }
65 | ```
66 |
67 | The remaining parts are translated to ` map, flatMap, withFilter` according to standard for-comprehension rules.
68 |
69 |
70 | ## Random Generators with For-Expressions
71 |
72 | The `map` and `flatMap` methods can be overridden to make a for-expression versatile, for example to generate random elements from an arbitrary collection like lists, sets etc. Define the following trait `Generator` to do this.
73 |
74 | ```scala
75 | trait Generator[+T] { self =>
76 | def generate: T
77 | def map[S](f: T => S) : Generator[S] = new Generator[S] {
78 | def generate = f(self.generate)
79 | }
80 | def flatMap[S](f: T => Generator[S]) : Generator[S] = new Generator[S] {
81 | def generate = f(self.generate).generate
82 | }
83 | }
84 | ```
85 |
86 | Let's define a basic integer random generator as
87 |
88 | ```scala
89 | val integers = new Generator[Int] {
90 | val rand = new java.util.Random
91 | def generate = rand.nextInt()
92 | }
93 | ```
94 |
95 | With these definition, and a basic definition of `integer` generator, we can map it to other domains like `booleans, pairs, intervals` using for-expression magic
96 |
97 |
98 | ```scala
99 | val booleans = for {x <- integers} yield x > 0
100 | val pairs = for {x <- integers; y<- integers} yield (x, y)
101 | def interval(lo: Int, hi: Int) : Generator[Int] = for { x <- integers } yield lo + math.abs(x) % (hi - lo)
102 | ```
103 |
104 |
105 | ## Monads
106 |
107 | A monad is a parametric type `M[T]` with two operations: `flatMap` and `unit`.
108 |
109 | ```scala
110 | trait M[T] {
111 | def flatMap[U](f: T => M[U]) : M[U]
112 | def unit[T](x: T) : M[T]
113 | }
114 | ```
115 |
116 | These operations must satisfy three important properties:
117 |
118 | 1. **Associativity:** `(x flatMap f) flatMap g == x flatMap (y => f(y) flatMap g)`
119 | 2. **Left unit:** `unit(x) flatMap f == f(x)`
120 |
121 | 3. **Right unit:** `m flatMap unit == m`
122 |
123 | Many standard Scala Objects like `List, Set, Option, Gen` are monads with identical implementation of `flatMap` and specialized implementation of `unit`. An example of non-monad is a special `Try` object that fails with a non-fatal exception because it fails to satisfy Left unit (See lectures).
124 |
125 |
126 | ## Monads and For-Expression
127 |
128 | Monads help simplify for-expressions.
129 |
130 | **Associativity** helps us "inline" nested for-expressions and write something like
131 |
132 | ```scala
133 | for { x <- e1; y <- e2(x) ... }
134 | ```
135 |
136 | **Right unit** helps us eliminate for-expression using the identity
137 |
138 | ```scala
139 | for{x <- m} yield x == m
140 | ```
141 |
142 |
143 | ## Pure functional programming
144 |
145 | In a pure functional state, programs are side-effect free, and the concept of time isn't important (i.e. redoing the same steps in the same order produces the same result).
146 |
147 | When evaluating a pure functional expression using the substitution model, no matter the evaluation order of the various sub-expressions, the result will be the same (some ways may take longer than others). An exception may be in the case where a sub-expression is never evaluated (e.g. second argument) but whose evaluation would loop forever.
148 |
149 |
150 | ## Mutable state
151 |
152 | In a reactive system, some states eventually need to be changed in a mutable fashion. An object has a state if its behavior has a history. Every form of mutable state is constructed from variables:
153 |
154 | ```scala
155 | var x: String = "abc"
156 | x = "hi"
157 | var nb = 42
158 | ```
159 |
160 | The use of a stateful expression can complexify things. For a start, the evaluation order may matter. Also, the concept of identity and change gets more complex. When are two expressions considered the same? In the following (pure functional) example, x and y are always the same (concept of referential transparency):
161 |
162 | ```scala
163 | val x = E; val y = E
164 | val x = E; val y = x
165 | ```
166 |
167 | But when a stateful variable is involved, the concept of equality is not as straightforward. "Being the same" is defined by the property of **operational equivalence**. x and y are operationally equivalent if no possible test can distinguish between them.
168 |
169 | Considering two variables x and y, if you can create a function f so that f(x, y) returns a different result than f(x, x) then x and y are different. If no such function exist x and y are the same.
170 |
171 | As a consequence, the substitution model ceases to be valid when using assignments.
172 |
173 |
174 | ## Loops
175 |
176 | Variables and assignments are enough to model all programs with mutable states and loops in essence are not required. Loops can be modeled using functions and lazy evaluation. So, the expression
177 |
178 | ```scala
179 | while (condition) { command }
180 | ```
181 |
182 | can be modeled using function WHILE as
183 |
184 | ```scala
185 | def WHILE(condition: => Boolean)(command: => Unit): Unit =
186 | if (condition) {
187 | command
188 | WHILE(condition)(command)
189 | }
190 | else ()
191 | ```
192 |
193 | **Note:**
194 | * Both **condition** and **command** are **passed by name**
195 | * **WHILE** is **tail recursive**
196 |
197 |
198 | ## For loop
199 |
200 | The treatment of for loops is similar to the For-Comprehensions commonly used in functional programming. The general expression for for loop equivalent in Scala is
201 |
202 | ```scala
203 | for(v1 <- e1; v2 <- e2; ...; v_n <- e_n) command
204 | ```
205 |
206 | Note a few subtle differences from a For-expression. There is no `yield` expression, `command` can contain mutable states and `e1, e2, ..., e_n` are expressions over arbitrary Scala collections. This for loop is translated by Scala using a **foreach** combinator defined over any arbitrary collection. The signature for **foreach** over collection **T** looks like this
207 |
208 | ```scala
209 | def foreach(f: T => Unit) : Unit
210 | ```
211 |
212 | Using foreach, the general for loop is recursively translated as follows:
213 |
214 | ```scala
215 | for(v1 <- e1; v2 <- e2; ...; v_n <- e_n) command =
216 | e1 foreach (v1 => for(v2 <- e2; ...; v_n <- e_n) command)
217 | ```
218 |
219 |
220 | ## Monads and Effect
221 |
222 | Monads and their operations like flatMap help us handle programs with side-effects (like exceptions) elegantly. This is best demonstrated by a Try-expression. Note: Try-expression is not strictly a Monad because it does not satisfy all three laws of Monad mentioned above. Although, it still helps handle expressions with exceptions.
223 |
224 |
225 | #### Try
226 |
227 | The parametric Try class as defined in Scala.util looks like this:
228 |
229 | ```scala
230 | abstract class Try[T]
231 | case class Success[T](elem: T) extends Try[T]
232 | case class Failure(t: Throwable) extends Try[Nothing]
233 | ```
234 |
235 | `Try[T]` can either be `Success[T]` or `Failure(t: Throwable)`
236 |
237 | ```scala
238 | import scala.util.{Try, Success, Failure}
239 |
240 | def answerToLife(nb: Int) : Try[Int] = {
241 | if (nb == 42) Success(nb)
242 | else Failure(new Exception("WRONG"))
243 | }
244 |
245 | answerToLife(42) match {
246 | case Success(t) => t // returns 42
247 | case failure @ Failure(e) => failure // returns Failure(java.Lang.Exception: WRONG)
248 | }
249 | ```
250 |
251 |
252 | Now consider a sequence of scala method calls:
253 |
254 | ```scala
255 | val o1 = SomeTrait()
256 | val o2 = o1.f1()
257 | val o3 = o2.f2()
258 | ```
259 |
260 | All of these method calls are synchronous, blocking and the sequence computes to completion as long as none of the intermediate methods throw an exception. But what if one of the methods, say `f2` does throw an exception? The `Try` class defined above helps handle these exceptions elegantly, provided we change return types of all methods `f1, f2, ...` to `Try[T]`. Because then, the sequence of method calls translates into an elegant for-comprehension:
261 |
262 | ```scala
263 | val o1 = SomeTrait()
264 | val ans = for {
265 | o2 <- o1.f1()
266 | o3 <- o2.f2()
267 | } yield o3
268 | ```
269 |
270 | This transformation is possible because `Try` satisfies 2 properties related to `flatMap` and `unit` of a **monad**. If any of the intermediate methods `f1, f2` throws and exception, value of `ans` becomes `Failure`. Otherwise, it becomes `Success[T]`.
271 |
272 |
273 | ## Monads and Latency
274 |
275 | The Try Class in previous section worked on synchronous computation. Synchronous programs with side effects block the subsequent instructions as long as the current computation runs. Blocking on expensive computation might render the entire program slow!. **Future** is a type of monad the helps handle exceptions and latency and turns the program in a non-blocking asynchronous program.
276 |
277 |
278 | #### Future
279 |
280 | Future trait is defined in scala.concurrent as:
281 |
282 | ```scala
283 | trait Future[T] {
284 | def onComplete(callback: Try[T] => Unit)
285 | (implicit executor: ExecutionContext): Unit
286 | }
287 | ```
288 |
289 | The Future trait contains a method `onComplete` which itself takes a method, `callback` to be called as soon as the value of current Future is available. The insight into working of Future can be obtained by looking at its companion object:
290 |
291 | ```scala
292 | object Future {
293 | def apply(body: => T)(implicit context: ExecutionContext): Future[T]
294 | }
295 | ```
296 |
297 | This object has an apply method that starts an asynchronous computation in current context, returns a `Future` object. We can then subscribe to this `Future` object to be notified when the computation finishes.
298 |
299 | ```scala
300 | import scala.util.{Try, Success, Failure}
301 | import scala.concurrent._
302 | import ExecutionContext.Implicits.global
303 |
304 | // The function to be run asyncronously
305 | val answerToLife: Future[Int] = future {
306 | 42
307 | }
308 |
309 | // These are various callback functions that can be defined
310 | answerToLife onComplete {
311 | case Success(result) => result
312 | case Failure(t) => println("An error has occured: " + t.getMessage)
313 | }
314 |
315 | answerToLife onSuccess {
316 | case result => result
317 | }
318 |
319 | answerToLife onFailure {
320 | case t => println("An error has occured: " + t.getMessage)
321 | }
322 |
323 | answerToLife // only works if the future is completed
324 | ```
325 |
326 |
327 | #### Combinators on Future
328 |
329 | A `Future` is a `Monad` and has `map`, `filter`, `flatMap` defined on it. In addition, Scala's Futures define two additional methods:
330 |
331 | ```scala
332 | def recover(f: PartialFunction[Throwable, T]): Future[T]
333 | def recoverWith(f: PartialFunction[Throwable, Future[T]]): Future[T]
334 | ```
335 |
336 | These functions return robust features in case current features fail.
337 |
338 | Finally, a `Future` extends from a trait called `Awaitable` that has two blocking methods, `ready` and `result` which take the value 'out of' the Future. The signatures of these methods are
339 |
340 | ```scala
341 | trait Awaitable[T] extends AnyRef {
342 | abstract def ready(t: Duration): Unit
343 | abstract def result(t: Duration): T
344 | }
345 | ```
346 |
347 | Both these methods block the current execution for a duration of `t`. If the future completes its execution, they return: `result` returns the actual value of the computation, while `ready` returns a Unit. If the future fails to complete within time t, the methods throw a `TimeoutException`.
348 |
349 | `Await` can be used to wait for a future with a specified timeout, e.g.
350 |
351 | ```scala
352 | userInput: Future[String] = ...
353 | Await.result(userInput, 10 seconds) // waits for user input for 10 seconds, after which throws a TimeoutException
354 | ```
355 |
356 |
357 | #### async and await
358 |
359 | Async and await allow to run some part of the code aynchronously. The following code computes asynchronously any future inside the `await` block
360 |
361 | ```scala
362 | import scala.async.Async._
363 |
364 | def retry(noTimes: Int)(block: => Future[T]): Future[T] = async {
365 | var i = 0;
366 | var result: Try[T] = Failure(new Exception("Problem!"))
367 | while (i < noTimes && result.isFailure) {
368 | result = await { Try(block) }
369 | i += 1
370 | }
371 | result.get
372 | }
373 | ```
374 |
375 |
376 | #### Promises
377 |
378 | A Promise is a monad which can complete a future, with a value if successful (thus completing the promise) or with an exception on failure (failing the promise).
379 |
380 | ```scala
381 | trait Promise[T] {
382 | def future: Future[T]
383 | def complete(result: Try[T]): Unit // to call when the promise is completed
384 | def tryComplete(result: Try[T]): Boolean
385 | }
386 | ```
387 |
388 | It is used as follows:
389 |
390 | ```scala
391 | val p = Promise[T] // defines a promise
392 | p.future // returns a future that will complete when p.complete() is called
393 | p.complete(Try[T]) // completes the future
394 | p success T // successfully completes the promise
395 | p failure (new ) // failed with an exception
396 | ```
397 |
398 | ## Observables
399 | Observables are asynchronous streams of data. Contrary to Futures, they can return multiple values.
400 |
401 | ```scala
402 | trait Observable[T] {
403 | def Subscribe(observer: Observer[T]): Subscription
404 | }
405 |
406 | trait Observer[T] {
407 | def onNext(value: T): Unit // callback function when there's a next value
408 | def onError(error: Throwable): Unit // callback function where there's an error
409 | def onCompleted(): Unit // callback function when there is no more value
410 | }
411 |
412 | trait Subscription {
413 | def unsubscribe(): Unit // to call when we're not interested in recieving any more values
414 | }
415 | ```
416 |
417 | Observables can be used as follows:
418 |
419 | ```scala
420 | import rx.lang.scala._
421 |
422 | val ticks: Observable[Long] = Observable.interval(1 seconds)
423 | val evens: Observable[Long] = ticks.filter(s => s%2 == 0)
424 |
425 | val bugs: Observable[Seq[Long]] = ticks.buffer(2, 1)
426 | val s = bugs.subscribe(b=>println(b))
427 |
428 | s.unsubscribe()
429 | ```
430 |
431 | Some observable functions (see more at http://rxscala.github.io/scaladoc/index.html#rx.lang.scala.Observable):
432 |
433 | - `Observable[T].flatMap(T => Observable[T]): Observable[T]` merges a list of observables into a single observable in a non-deterministic order
434 | - `Observable[T].concat(T => Observable[T]): Observable[T]` merges a list of observables into a single observable, putting the results of the first observable first, etc.
435 | - `groupBy[K](keySelector: T=>K): Observable[(K, Observable[T])]` returns an observable of observables, where the element are grouped by the key returned by `keySelector`
436 |
437 | #### Subscriptions
438 |
439 | Subscriptions are returned by Observables to allow to unsubscribe. With hot observables, all subscribers share the same source, which produces results independent of subscribers. With cold observables each subscriber has its own private source. If there is no subscriber no computation is performed.
440 |
441 | Subscriptions have several subtypes: `BooleanSubscription` (was the subscription unsubscribed or not?), `CompositeSubscription` (collection of subscriptions that will be unsubscribed all at once), `MultipleAssignmentSubscription` (always has a single subscription at a time)
442 |
443 | ```scala
444 | val subscription = Subscription { println("Bye") }
445 | subscription.unsubscribe() // prints the message
446 | subscription.unsubscribe() // doesn't print it again
447 | ```
448 |
449 | #### Creating Rx Streams
450 |
451 | Using the following constructor that takes an Observer and returns a Subscription
452 |
453 | ```scala
454 | object Observable {
455 | def apply[T](subscribe: Observer[T] => Subscription): Observable[T]
456 | }
457 | ```
458 |
459 | It is possible to create several observables. The following functions suppose they are part of an Observable type (calls to `subscribe(...)` implicitely mean `this.subscribe(...)`):
460 |
461 | ```scala
462 | // Observable never: never returns anything
463 | def never(): Observable[Nothing] = Observable[Nothing](observer => { Subscription {} })
464 |
465 | // Observable error: returns an error
466 | def apply[T](error: Throwable): Observable[T] =
467 | Observable[T](observer => {
468 | observer.onError(error)
469 | Subscription {}
470 | }
471 | )
472 |
473 | // Observable startWith: prepends some elements in front of an Observable
474 | def startWith(ss: T*): Observable[T] = {
475 | Observable[T](observer => {
476 | for(s <- ss) observer onNext(s)
477 | subscribe(observer)
478 | })
479 | })
480 |
481 | // filter: filters results based on a predicate
482 | def filter(p: T=>Boolean): Observable[T] = {
483 | Observable[T](observer => {
484 | subscribe(
485 | (t: T) => { if(p(t)) observer.onNext(t) },
486 | (e: Thowable) => { observer.onError(e) },
487 | () => { observer.onCompleted() }
488 | )
489 | })
490 | }
491 |
492 | // map: create an observable of a different type given a mapping function
493 | def map(f: T=>S): Observable[S] = {
494 | Observable[S](observer => {
495 | subscribe(
496 | (t: T) => { observer.onNext(f(t)) },
497 | (e: Thowable) => { observer.onError(e) },
498 | () => { observer.onCompleted() }
499 | )
500 | }
501 | }
502 |
503 | // Turns a Future into an Observable with just one value
504 | def from(f: Future[T])(implicit execContext: ExecutionContext): Observable[T] = {
505 | val subject = AsyncSubject[T]()
506 | f onComplete {
507 | case Failure(e) => { subject.onError(e) }
508 | case Success(c) => { subject.onNext(c); subject.onCompleted() }
509 | }
510 | subject
511 | }
512 | ```
513 |
514 |
515 | #### Blocking Observables
516 |
517 | `Observable.toBlockingObservable()` returns a blocking observable (to use with care). Everything else is non-blocking.
518 |
519 | ```scala
520 | val xs: Observable[Long] = Observable.interval(1 second).take(5)
521 | val ys: List[Long] = xs.toBlockingObservable.toList
522 | ```
523 |
524 | #### Schedulers
525 |
526 | Schedulers allow to run a block of code in a separate thread. The Subscription returned by its constructor allows to stop the scheduler.
527 |
528 | ```scala
529 | trait Observable[T] {
530 | def observeOn(scheduler: Scheduler): Observable[T]
531 | }
532 |
533 | trait Scheduler {
534 | def schedule(work: => Unit): Subscription
535 | def schedule(work: Scheduler => Subscription): Subscription
536 | def schedule(work: (=>Unit)=>Unit): Subscription
537 | }
538 |
539 | val scheduler = Scheduler.NewThreadScheduler
540 | val subscription = scheduler.schedule { // schedules the block on another thread
541 | println("Hello world")
542 | }
543 |
544 |
545 | // Converts an iterable into an observable
546 | // works even with an infinite iterable
547 | def from[T](seq: Iterable[T])
548 | (implicit scheduler: Scheduler): Obserable[T] = {
549 | Observable[T](observer => {
550 | val it = seq.iterator()
551 | scheduler.schedule(self => { // the block between { ... } is run in a separate thread
552 | if (it.hasnext) { observer.onNext(it.next()); self() } // calling self() schedules the block of code to be executed again
553 | else { observer.onCompleted() }
554 | })
555 | })
556 | }
557 | ```
558 |
559 | ## Actors
560 |
561 | Actors represent objects and their interactions, resembling human organizations. They are useful to deal with the complexity of writing multi-threaded applications (with their synchronizations, deadlocks, etc.)
562 |
563 | ```scala
564 | type Receive = PartialFunction[Any, Unit]
565 |
566 | trait Actor {
567 | def receive: Receive
568 | }
569 | ```
570 |
571 | An actor has the following properties:
572 | - It is an object with an identity
573 | - It has a behavior
574 | - It only interacts using asynchronous message
575 |
576 | Note: to use Actors in Eclipse you need to run a Run Configuration whose main class is `akka.Main` and whose Program argument is the full Main class name
577 |
578 | An actor can be used as follows:
579 |
580 | ```scala
581 | import akka.actor._
582 |
583 | class Counter extends Actor {
584 | var count = 0
585 | def receive = {
586 | case "incr" => count += 1
587 | case ("get", customer: ActorRef) => customer ! count // '!' means sends the message 'count' to the customer
588 | case "get" => sender ! count // same as above, except sender means the sender of the message
589 | }
590 | }
591 | ```
592 |
593 | #### The Actor's Context
594 |
595 | The Actor type describes the behavior (represented by a Receive, which is a PartialFunction), the execution is done by its ActorContext. An Actor can change its behavior by either pushing a new behavior on top of a stack or just purely replace the old behavior.
596 |
597 | ```scala
598 | trait ActorContext {
599 | def become(behavior: Receive, discardOld: Boolean = true): Unit // changes the behavior
600 | def unbecome(): Unit // reverts to the previous behavior
601 | def actorOf(p: Props, name: String): ActorRef // creates a new actor
602 | def stop(a: ActorRef): Unit // stops an actor
603 | def watch(target: ActorRed): ActorRef // watches whenever an Actor is stopped
604 | def unwatch(target: ActorRed): ActorRef // unwatches
605 | def parent: ActorRef // the Actor's parent
606 | def child(name: String): Option[ActorRef] // returns a child if it exists
607 | def children: Iterable[ActorRef] // returns all supervised children
608 | }
609 |
610 | class myActor extends Actor {
611 | ...
612 | context.parent ! aMessage // sends a message to the parent Actor
613 | context.stop(self) // stops oneself
614 | ...
615 | }
616 | ```
617 |
618 |
619 | The following example is changing the Actor's behavior any time the amount is changed. The upside of this method is that 1) the state change is explicit and done by calling `context.become()` and 2) the state is scoped to the current behavior.
620 |
621 | ```scala
622 | class Counter extends Actor {
623 | def counter(n: Int): Receive = {
624 | case "incr" => context.become(counter(n + 1))
625 | case "get" => sender ! n
626 | }
627 | def receive = counter(0)
628 | }
629 | ```
630 |
631 | #### Children and hierarchy
632 |
633 | Each Actor can create children actors, creating a hierarchy.
634 |
635 | ```scala
636 | class Main extends Actor {
637 | val counter = context.actorOf(Props[Counter], "counter") // creates a Counter actor named "counter"
638 |
639 | counter ! "incr"
640 | counter ! "incr"
641 | counter ! "incr"
642 | counter ! "get"
643 |
644 | def receive = { // receives the messages from Counter
645 | case count: Int =>
646 | println(s"count was $count")
647 | context.stop(self)
648 | }
649 | }
650 | }
651 | ```
652 |
653 | Each actor maintains a list of the actors it created:
654 | - the child is added to the list when context.actorOf returns
655 | - the child is removed when Terminated is received
656 | - an actor name is available IF there is no such child. Actors are identified by their names, so they must be unique.
657 |
658 | #### Message Processing Semantics
659 |
660 | There is no direct access to an actor behavior. Only messages can be sent to known addresses (`ActorRef`). Those addresses can be either be oneself (`self`), the address returned when creating a new actor, or when received by a message (e.g. `sender`)
661 |
662 | Actors are completely insulated from each other except for messages they send each other. Their computation can be run concurrently. However, a specific actor is single-threaded - its messages are received sequentially. Processing a message is the atomic unit of execution and cannot be interrupted.
663 |
664 | It is good practice to define an Actor's messages in its companion object. Here, each operation is effectively synchronized as all messages are serialized.
665 |
666 | ```scala
667 | object BankAccount {
668 | case class Deposit(amount: BigInt) {
669 | require(amount > 0)
670 | }
671 | case class Withdraw(amount: BigInt) {
672 | require(amount > 0)
673 | }
674 | case object Done
675 | case object Failed
676 | }
677 |
678 | class BankAccount extends Actor {
679 | import BankAccount._
680 |
681 | var balance = BigInt(0)
682 |
683 | def receive = {
684 | case Deposit(amount) => balance += amount
685 | sender ! Done
686 | case Withdraw(amount) if amount <= balance => balance -= amount
687 | sender ! Done
688 | case _ => sender ! Failed
689 | }
690 | }
691 | ```
692 |
693 | Note that `pipeTo` can be used to foward a message (`theAccount deposit(500) pipeTo sender`)
694 |
695 | Because communication is through messages, there is no delivery guarantee. Hence the need of messages of acknowledgement and/or repeat. There are various strategies to deal with this:
696 | - at-most-once: send a message, without guarantee it will be received
697 | - at-least-once: keep sending messages until an ack is received
698 | - exactly-once: keep sending messages until an ack is received, but the recipient will only process the first message
699 |
700 | You can call `context.setReceiveTimeout(10.seconds)` that sets a timeout:
701 |
702 | ```scala
703 | def receive = {
704 | case Done => ...
705 | case ReceiveTimeout => ...
706 | }
707 | ```
708 |
709 | The Akka library also includes a scheduler that sends a message or executes a block of code after a certain delay:
710 |
711 | ```scala
712 | trait Scheduler {
713 | def scheduleOnce(delay: FiniteDuration, target: ActorRef, msg: Any)
714 | def scheduleOnce(delay: FiniteDuration)(block: => Unit)
715 | }
716 | ```
717 |
718 | #### Designing Actor Systems
719 |
720 | When designing an Actor system, it is useful to:
721 | - visualize a room full of people (i.e. the Actors)
722 | - consider the goal to achieve
723 | - split the goal into subtasks that can be assigned to the various actors
724 | - who needs to talk to whom?
725 | - remember that you can easily create new Actors, even short-lived ones
726 | - watch out for any blocking part
727 | - prefer immutable data structures that can safely be shared
728 | - do not refer to actor state from code running asynchronously
729 |
730 | Consider a Web bot that recursively download content (down to a certain depth):
731 | - one Client Actor, which is sending download requests
732 | - one Receptionist Actor, responsible for accepting incoming download requests from Clients. The Receptionist forwards the request to the Controller
733 | - one Controller Actor, noting the pages already downloaded and dispatching the download jobs to Getter actors
734 | - one or more Getter Actors whose job is to download a URL, check its links and tell the Controller about those links
735 | - each message between the Controller and the Getter contains the depth level
736 | - once this is done, the Controller notifies the Receptionist, who remembers the Client who asked for that request and notifies it
737 |
738 | #### Testing Actor Systems
739 |
740 | Tests can only verify externally observable effects. Akka's TestProbe allows to check that:
741 |
742 | ```scala
743 | implicit val system = ActorSystem("TestSys")
744 | val myActor = system.actorOf(Props[MyActor])
745 | val p = TestProbe()
746 | p.send(myActor, "Message")
747 | p.exceptMsg("Ack")
748 | p.send(myActor, "Message")
749 | p.expectNoMsg(1.second)
750 | system.shutdown()
751 | ```
752 |
753 | It can also be run from inside TestProbe:
754 |
755 | ```scala
756 | new TestKit(ActorSystem("TestSys")) with ImplicitSender {
757 | val myActor = system.actorOf(Props[MyActor])
758 | myActor ! "Message"
759 | expectMsg("Ack")
760 | send(myActor, "Message")
761 | expectNoMsg(1.second)
762 | system.shutdown()
763 | }
764 | ```
765 |
766 | You can use dependency injection when the system relies from external sources, like overriding factory methods that work as follows:
767 | - have a method that will call `Props[MyActor]`
768 | - its result is called by context.actorOf()
769 | - the test can define a "fake Actor" (`object FakeMyActor extends MyActor { ... }`) that will override the method
770 |
771 | You should start first the "leaves" actors and work your way to the parent actors.
772 |
773 | #### Logging Actor Systems
774 |
775 | You can mix in your actor with `ActorLogging`, and use various log methods such as `log.debug` or `log.info`.
776 |
777 | To see all the messages the actor is receiving, you can also define `receive` method as a `LoggingReceive`.
778 |
779 | ```scala
780 | def receive: Receive = LoggingReceive {
781 | case Replicate =>
782 | case Snapshot =>
783 | }
784 | ```
785 |
786 | To see the log messages turn on akka debug level by adding the following in your run configuration.
787 |
788 | -Dakka.loglevel=DEBUG -Dakka.actor.debug.receive=on
789 |
790 | Alternatively, create a file named `src/test/resources/application.conf` with the following content:
791 |
792 | akka {
793 | loglevel = "DEBUG"
794 | actor {
795 | debug {
796 | receive = on
797 | }
798 | }
799 | }
800 |
801 | #### Failure handling with Actors
802 |
803 | What happens when an error happens with an actor? Where shall failures go? With the Actor models, Actors work together in teams (systems) and individual failures are handled by the team leader.
804 |
805 | Resilience demands containment (i.e. the failure is isolated so that it cannot spread to other components) and delegation of failure (i.e. it is handled by someone else and not the failed component)
806 |
807 | In the Supervisor model, the Supervisor needs to create its subordinates and will handle the exceptions encountered by its children. If a child fails, the supervisor may decide to stop it (`stop` message) or to restart it to get it back to a known good state and initial behavior (in Akka, the ActorRef stays valid after a restart).
808 |
809 | An actor can decide a strategy by overriding `supervisorStrategy`, e.g.
810 |
811 | ```scala
812 | class myActor extends Actor {
813 | override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 5) {
814 | case _: Exception => SupervisorStrategy.Restart
815 | }
816 | }
817 | ```
818 |
819 | #### Lifecycle of an Actor
820 |
821 | - An Actor will have its context create a child Actor, and gets `preStart()` called.
822 | - In case of a failure, the supervisor gets consulted. The supervisor can stop the child or restart it (a restart is not externally visible). In case of a restart, the child Actor's `preRestart()` gets called. A new instance of the actor is created, after which its `postRestart()` method gets called. No message gets processed between the failure and the restart.
823 | - An actor can be restarted several times.
824 | - An actor can finally be stopped. It sends Stop to the context and its `postStop()` method will be called.
825 |
826 | An Actor has the following methods that can be overridden:
827 |
828 | ```scala
829 | trait Actor {
830 | def preStart(): Unit
831 | def preRestart(reason: Throwable, message: Option[Any]): Unit // the default behavior is to stop all children
832 | def postRestart(reason: Throwable): Unit // the default behavior is to call preStart()
833 | def postStop(): Unit
834 | }
835 | ```
836 |
837 | #### Lifecycle Monitoring
838 |
839 | To remove the ambiguity where a message doesn't get a response because the recipient stopped or because the network is down, Akka supports Lifecycle Monitoring, aka DeathWatch:
840 | - an Actor registers its interest using `context.watch(target)`
841 | - it will receive a `Terminated(target)` message when the target stops
842 | - it will not receive any direct messages from the target thereafter
843 |
844 | The watcher receives a `Terminated(actor: ActorRef)` message:
845 | - It is a special message that our code cannot send
846 | - It comes with two implicit boolean flags: `existenceConfirmed` (was the watch sent when the target was still existing?) and `addressTerminated` (the watched actor was detected as unreachable)
847 | - Terminated messages are handled by the actor context, so cannot be forwarded
848 |
849 | #### The Error Kernel pattern
850 |
851 | Keep important data near the root, delegate the risk to the leaves
852 | - restarts are recursive
853 | - as a result, restarts are more frequent near the leaves
854 | - avoid restarting Actors with important states
855 |
856 | #### EventStream
857 |
858 | Because Actors can only send messages to a known address, the EventStream allows publication of messages to an unknown audience
859 |
860 | ```scala
861 | trait EventStream {
862 | def subscribe(subscriber: ActorRef, topic: Class[_]): Boolean
863 | def unsubscribe(subscriber: ActorRef, topic: Class[_]): Boolean
864 | def unsubscribe(subscriber: ActorRef): Unit
865 | def publish(event: AnyRef): Unit
866 | }
867 |
868 |
869 | class MyActor extends Actor {
870 | context.system.eventStream.subscribe(self, classOf[LogEvent])
871 | def receive = {
872 | case e: LogEvent => ...
873 | }
874 | override def postStop(): Unit = {
875 | context.system.eventStream.unsubscribe(self)
876 | }
877 | }
878 | ```
879 |
880 | Unhanlded messages are passed to the Actor's `unhandled(message: Any)` method.
881 |
882 | #### Persistent Actor State
883 |
884 | The state of an Actor can be stored on disk to prevent data loss in case of a system failure.
885 |
886 | There are two ways for persisting state:
887 | - in-place updates mimics what is stored on the disk. This solution allows a fast recovery and limits the space used on the disk.
888 | - persist changes in append-only fashion. This solution allows fast updates on the disk. Because changes are immutable they can be freely be replicated. Finally it allows to analyze the history of a state.
889 | - Command-Sourcing: persists the messages before processing them, persist acknowledgement when processed. Recovery works by sending the messages to the actor. A persistent Channel discards the messages already sent to the other actors
890 | - Event-Sourcing: Generate change requests (events) instead of modifying the local state. The events are sent to the log that stores them. The actor can either update its state when sending the event to the log or wait for the log to contact it back (in which case it can buffer any message while waiting for the log).
891 | - In both cases, immutable snapshots can be made at certain points of time. Recovery only applies recent changes to the latest snapshot.
892 |
893 | Each strategy have their upsides and downsides in terms of performance to change the state, recover the state, etc.
894 |
895 | The `stash` trait allows to buffer, e.g.
896 |
897 | ```scala
898 | class MyActor extends Actor with Stash {
899 | var state: State = ...
900 | def receive = {
901 | case NewState(text) if !state.disabled =>
902 | ... // sends the event to the log
903 | context.become(waiting, discardOld = false)
904 | }
905 | def waiting(): Receive = {
906 | case e: Event =>
907 | state = state.updated(e) // updates the state
908 | context.unbecome(); // reverts to the previous behavior
909 | unstashAll() // processes all the stashed messages
910 | case _ => stash() // stashes any message while waiting
911 | }
912 | }
913 | ```
914 |
915 | ## Clusters
916 |
917 | Actors are designed to be distributed. Which means they could be run on different cores, CPUs or even machines.
918 |
919 | When actors are distributed across a network, several problems can arise. To begin with, data sharing can only be by value and not by reference. Other networking: low bandwidth, partial failure (some messages never make it)
920 |
921 | On a network, Actors have a path that allow to reach them. An `ActorPath` is the full name (e.g. akka.tcp://HelloWorld@198.2.12.10:6565/user/greeter or akka://HelloWorld/user/greeter), whether the actor exists or not, whereas `ActorRef` points to an actor which was started and contains a UID (e.g. akka://HelloWorld/user/greeter#234235234).
922 |
923 | You can use `context.actorSelection(path) ! Identify((path, sender))` to convert an ActorPath into an ActorRef. An `ActorIdentity((path: ActorPath, client: ActorRef), ref: Option(ActorRef)` message is then sent back with `ref` being the ActorRef if there is any.
924 |
925 | It is also possible to get using `context.actorSelection("...")` which can take a local ("child/grandchild", "../sibling"), full path ("/user/myactor") or wildcards ("/user/controllers/*")
926 |
927 | #### Creating clusters
928 |
929 | A cluster is a set of nodes where all nodes know about each other and work to collaborate on a common task. A node can join a cluster by either sending a join request to any node of the cluster. It is accepted if all the nodes agree and know about the new node.
930 |
931 | The akka-cluster module must be installed and properly configured (akka.actor.provider = akka.cluster.ClusterActorRefProvider). To start a cluster, a node must start a cluster and join it:
932 |
933 | ```scala
934 | class MyActor extends Actor {
935 | val cluster = Cluster(context.system)
936 | cluster.subscribe(self, classOf[ClusterEvent.MemberUp])
937 | cluster.join(cluster.selfAddress)
938 |
939 | def receive = {
940 | case ClusterEvent.MemberUp(member) =>
941 | if (member.address != cluster.selfAddress) {
942 | // someone joined
943 | }
944 | }
945 | }
946 |
947 | class MyOtherActor extends Actor {
948 | val cluster = Cluster(context.system)
949 | cluster.subscribe(self, classOf[ClusterEvent.MemberUp])
950 | val main = cluster.selfAddress.copy(port = Some(2552)) // default port
951 | cluster.join(cluster.selfAddress)
952 |
953 | def receive = {
954 | case ClusterEvent.MemberRemoved(m, _) => if (m.address == main) context.stop(self)
955 | }
956 | }
957 | ```
958 |
959 | It is possible to create a new actor on a remote node
960 |
961 | ```scala
962 | val node: Address = ...
963 | val props = Props[MyClass].withDeploy(Deploy(scope = RemoteScope(node)))
964 | val controller = context.actorOf(props, "myclass")
965 | ```
966 |
967 | #### Eventual Consistency
968 |
969 | - Strong consistency: after an update completes, all reads will return the updated value
970 | - Weak consistency: after an update, conditions need to be met until reads return the updated value (inconsistency window)
971 | - Eventual Consistency (a form of weak consistency): once no more updates are made to an object there is a time after which all reads return the last written value.
972 |
973 | In a cluster, the data is propagated through messages. Which means that collaborating actors can be at most eventually consistent.
974 |
975 | #### Actor Composition
976 |
977 | Since an Actor is only defined by its accepted message types, its structure may change over time.
978 |
979 | Various patterns can be used with actors:
980 |
981 | - The Customer Pattern: typical request/reply, where the customer address is included in the original request
982 | - Interceptors: one-way proxy that does not need to keep state (e.g. a log)
983 | - The Ask Pattern: create a temporary one-off ask actor for receiving an email (you can use `import.pattern.ask` and the `?` send message method)
984 | - Result Aggregation: aggregate results from multiple actors
985 | - Risk Delegation: create a subordinate to perform a task that may fail
986 | - Facade: used for translation, validation, rate limitation, access control, etc.
987 |
988 | Here is a code sniplet using the ask and aggregation patterns:
989 |
990 | ```scala
991 | def receive = {
992 | case Message =>
993 | val response = for {
994 | result1 <- (actor1 ? Message1).mapTo[MyClass1]
995 | result2 <- (actor2 ? Message2).mapTo[MyClass2] // only called when result1 is received
996 | } yield ...
997 |
998 | response pipeTo sender
999 | }
1000 | ```
1001 |
1002 | #### Scalability
1003 |
1004 | Asynchronous messages passing enables vertical scalability (running the computation in parallel in the same node)
1005 | Location transparency enables horizontal scalability (running the computation on a cluster of multiple nodes)
1006 |
1007 | Low performance means the system is slow for a single client (high latency)
1008 | Low scalability means the system is fast when used by a single client (low latency) but slow when used by many clients (low bandwidth)
1009 |
1010 | With actors, scalability can be achieved by running several stateless replicas concurrently. The incoming messages are dispatched through routing. Routing actor(s) can either be stateful (e.g. round robin, adaptive routing) or stateless (e.g. random, consistent hashing)
1011 |
1012 | - In Adaptive Routing (stateful), routees tell the router about their queue sizes on a regular basis.
1013 | - In Consistent Hashing (stateless), the router is splitting incoming messages based on some criterion
1014 |
1015 | Stateful actors can be recovered based on a persisted state, but this means that 1) only one instance must be active at all time and 2) the routing is always to the active instance, buffering messages during recovery.
1016 |
1017 | #### Responsiveness
1018 |
1019 | Responsiveness is the ability to respond within a given time limit. If the goal of resilience is to be available, responsiveness implies resilience to overload scenarios.
1020 |
1021 | Several patterns can be implemented to achieve responsiveness:
1022 |
1023 | 1) Exploit parallelism, e.g.
1024 |
1025 | ```scala
1026 | def receive = {
1027 | case Message =>
1028 | val result1 = (actor1 ? Message1).mapTo[MyClass1]
1029 | val result2 = (actor2 ? Message2).mapTo[MyClass2] // both calls are run in parallel
1030 |
1031 | val response = for (r1 <- result1, r2 <- result2) yield { ... }
1032 | response pipeTo sender
1033 | }
1034 | ```
1035 |
1036 | 2) Load vs. Responsiveness: When incoming request rate rises, latency typically rises. Hence the need to avoid dependency of processing cost on load and add parallelism elastically, resizing routers.
1037 |
1038 | However, any system has its limits in which case processing gets backlogged.
1039 |
1040 | 3) The Circuit Breaker pattern (use akka `CircuitBreaker`) filters the number of requests that can come in when the sytem is under too much load, so that one subsystem being swamped does not affect the other subsystems.
1041 |
1042 | 4) With the Bulkheading patterns, one separates computing intensive parts from client-facing parts (e.g. on different nodes), the latter being able to run even if the backend fails.
1043 |
1044 | ```scala
1045 | Props[MyActor].withDispatcher("compute-jobs") // tells to run the actor on a different thread
1046 |
1047 | // If not, actors run on the default dispatcher
1048 | akka.actor.default-dispatcher {
1049 | executor = "fork-join-executor"
1050 | fork-join-executor {
1051 | parallelism-min = 8
1052 | parallelism-max = 64
1053 | parallelism-factor = 3.0
1054 | }
1055 | }
1056 |
1057 | compute-jobs.fork-join-executor {
1058 | parallelism-min = 4
1059 | parallelism-max = 4
1060 | }
1061 | ```
1062 |
1063 | 5) Use the Active-Active Configuration. Detecting failures takes time (usually a timeout). When this is not acceptable, instant failover is possible in active-active configurations where 3 nodes process the same request in parallel and send their reponses to the requester. Once the requester receives 2 matching results it considers it has its answer, and will proactively restart a node if it fails to respond within a certain time.
1064 |
1065 |
--------------------------------------------------------------------------------
/_config.yml:
--------------------------------------------------------------------------------
1 | name: Reactive Programming Scala
2 | markdown: redcarpet
--------------------------------------------------------------------------------
/_layouts/page.html:
--------------------------------------------------------------------------------
1 | {{ content }}
2 |
--------------------------------------------------------------------------------