├── .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 | --------------------------------------------------------------------------------