├── .gitignore ├── CourseraCodeSamplesReactiveProgramming-master.zip ├── LICENSE ├── README.md ├── ReactiveCheatSheet.pdf ├── akka-cluster-sharding-scala.zip ├── reactive-course source code-reactive-week1.zip ├── reactive-course source code-reactive-week2.zip ├── reactive.scala.learning-resources.pdf ├── w1 ├── quickcheck │ ├── build.sbt │ ├── project │ │ ├── ReactiveBuild.scala │ │ ├── build.properties │ │ └── plugins.sbt │ └── src │ │ ├── main │ │ └── scala │ │ │ ├── common │ │ │ └── package.scala │ │ │ └── quickcheck │ │ │ ├── Heap.scala │ │ │ └── QuickCheck.scala │ │ └── test │ │ └── scala │ │ └── quickcheck │ │ └── QuickCheckSuite.scala └── week1.quickcheck-instructions.pdf ├── w2 ├── calculator │ ├── build.sbt │ ├── project │ │ ├── ReactiveBuild.scala │ │ ├── build.properties │ │ └── plugins.sbt │ ├── src │ │ ├── main │ │ │ └── scala │ │ │ │ ├── calculator │ │ │ │ ├── Calculator.scala │ │ │ │ ├── Polynomial.scala │ │ │ │ ├── Signal.scala │ │ │ │ └── TweetLength.scala │ │ │ │ └── common │ │ │ │ └── package.scala │ │ └── test │ │ │ └── scala │ │ │ └── calculator │ │ │ └── CalculatorSuite.scala │ ├── web-ui │ │ ├── index.html │ │ └── src │ │ │ └── main │ │ │ └── scala │ │ │ └── calculator │ │ │ └── CalculatorUI.scala │ └── webui.sbt └── week2.calculator-instructions.pdf ├── w3 ├── nodescala │ ├── build.sbt │ ├── project │ │ ├── ReactiveBuild.scala │ │ ├── build.properties │ │ └── plugins.sbt │ └── src │ │ ├── main │ │ └── scala │ │ │ ├── common │ │ │ └── package.scala │ │ │ └── nodescala │ │ │ ├── Main.scala │ │ │ ├── nodescala.scala │ │ │ └── package.scala │ │ └── test │ │ └── scala │ │ └── nodescala │ │ └── tests.scala └── week3.nodescala-instructions.pdf ├── w4 ├── suggestions │ ├── build.sbt │ ├── project │ │ ├── ReactiveBuild.scala │ │ ├── build.properties │ │ └── plugins.sbt │ └── src │ │ ├── main │ │ ├── .DS_Store │ │ ├── resources │ │ │ └── suggestions │ │ │ │ └── wiki-icon.png │ │ └── scala │ │ │ ├── common │ │ │ └── package.scala │ │ │ └── suggestions │ │ │ ├── gui │ │ │ ├── SwingApi.scala │ │ │ ├── WikipediaApi.scala │ │ │ ├── WikipediaSuggest.scala │ │ │ └── package.scala │ │ │ ├── observablex │ │ │ ├── ObservableEx.scala │ │ │ └── SchedulerEx.scala │ │ │ ├── package.scala │ │ │ └── search │ │ │ └── Search.scala │ │ └── test │ │ └── scala │ │ └── suggestions │ │ ├── SwingApiTest.scala │ │ └── WikipediaApiTest.scala └── week4.suggestions-instructions.pdf ├── w5 ├── actorbintree │ ├── build.sbt │ ├── project │ │ ├── ReactiveBuild.scala │ │ ├── build.properties │ │ └── plugins.sbt │ └── src │ │ ├── main │ │ └── scala │ │ │ ├── actorbintree │ │ │ └── BinaryTreeSet.scala │ │ │ └── common │ │ │ └── package.scala │ │ └── test │ │ └── scala │ │ └── actorbintree │ │ └── BinaryTreeSuite.scala └── week5.actorbintree-instructions.pdf ├── w6 ├── kvstore │ ├── build.sbt │ ├── project │ │ ├── ReactiveBuild.scala │ │ ├── build.properties │ │ └── plugins.sbt │ └── src │ │ ├── main │ │ └── scala │ │ │ ├── common │ │ │ └── package.scala │ │ │ └── kvstore │ │ │ ├── Arbiter.scala │ │ │ ├── Persistence.scala │ │ │ ├── Replica.scala │ │ │ └── Replicator.scala │ │ └── test │ │ └── scala │ │ └── kvstore │ │ ├── IntegrationSpec.scala │ │ ├── Step1_PrimarySpec.scala │ │ ├── Step2_SecondarySpec.scala │ │ ├── Step3_ReplicatorSpec.scala │ │ ├── Step4_SecondaryPersistenceSpec.scala │ │ ├── Step5_PrimaryPersistenceSpec.scala │ │ ├── Step6_NewSecondarySpec.scala │ │ └── Tools.scala └── week6.kvstore-instructions.pdf └── worksheets ├── build.sbt ├── project ├── build.properties └── plugins.sbt └── src └── main └── scala ├── ws01PartialFunctions.sc ├── ws02Collections.sc ├── ws03ForQueries.sc ├── ws04ForTranslations.sc ├── ws05RandomGen.sc ├── ws06Monads.sc ├── ws2.1State.sc ├── ws2.2Identity.sc ├── ws2.3Loops.sc ├── ws2.4DescreteEventSimulator1.sc ├── ws2.5DescreteEventSimulator2.sc ├── ws2.6DescreteEventSimulator3.sc ├── ws2.7ClassicObserver.sc ├── ws2.8FRP.sc ├── ws2.9FRPImplementation.sc ├── ws3.10Promise.sc ├── ws3.1Monads1.sc ├── ws3.2Monads2.sc ├── ws3.3Latency1.sc ├── ws3.4Latency2.sc ├── ws3.5FutureCombinators1.sc ├── ws3.6FutureCombinators2.sc ├── ws3.7FutureCompose1.sc ├── ws3.8FutureCompose2.sc ├── ws3.9AsyncAwait.sc ├── ws4.2Iterables2Observables.sc ├── ws4.3Iterables2Observables2.sc ├── ws4.4ObservablesExample.sc ├── ws4.5RXOperators.sc ├── ws4.6Subscriptions.sc ├── ws4.7ObservablesSubjects.sc ├── ws4.8RXPotpourri.sc ├── ws4.9ObservableContract.sc ├── ws5.1WhyActors.sc ├── ws5.2ActorModel.sc ├── ws5.3MsgProcSemantics.sc ├── ws5.4DesigningActorSystem.sc ├── ws5.5TestingActorSystem.sc ├── ws6.1ActorsFailureHandling.sc ├── ws6.2MonitoringAndErrorKernel.sc ├── ws6.3PersistentActorState.sc ├── ws7.1ActorsDistributed1.sc ├── ws7.2ActorsDistributed2.sc ├── ws7.3EventualConsistency.sc ├── ws7.4ActorComposition.sc ├── ws7.5ScalabilityByActors.sc └── ws7.6Responsiveness.sc /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | # sbt specific 5 | .cache 6 | .history 7 | .lib/ 8 | dist/* 9 | target/ 10 | lib_managed/ 11 | src_managed/ 12 | project/boot/ 13 | project/plugins/project/ 14 | 15 | # Scala-IDE specific 16 | .scala_dependencies 17 | .worksheet 18 | -------------------------------------------------------------------------------- /CourseraCodeSamplesReactiveProgramming-master.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasnake/Principles-of-Reactive-Programming/66b432473a81342569394c331fc092b8f39505de/CourseraCodeSamplesReactiveProgramming-master.zip -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Principles of Reactive Programming (Scala) 2 | assignments and other code from Coursera class (now removed) 3 | * https://class.coursera.org/reactive-002 4 | * https://www.coursera.org/course/reactive 5 | 6 | 2019: Updated course: [Programming Reactive Systems (Principles of Reactive Programming in Scala)](https://www.edx.org/course/programming-reactive-systems)
7 | with some materials moved to [Functional Programming in Scala Specialization](https://www.coursera.org/specializations/scala) 8 | 9 | ### Video lectures: 10 | https://www.youtube.com/playlist?list=PLMhMDErmC1TdBMxd3KnRfYiBV2ELvLyxN 11 | 12 | ### Assignments 13 | - Week 1: QuickCheck/ScalaCheck 14 | - Week 2: Calculator 15 | - Week 3: NodeScala 16 | - Week 4: Wikipedia Suggestions 17 | - Week 5: Actor Binary Tree 18 | - Week 6: Replicated KV Store 19 | 20 | ### Sources 21 | - https://github.com/headinthebox/CourseraCodeSamplesReactiveProgramming 22 | - https://github.com/typesafehub/activator-akka-cluster-sharding-scala 23 | 24 | worksheets: code snippets from lectures 25 | 26 | Recap: Getting Started with Tools 27 | * Tools Setup for Linux (12:24) 28 | * Tools Setup for Mac OS X (12:17) 29 | * Tools Setup for Windows (10:37) 30 | * Tutorial: Working on the Programming Assignments (8:47) 31 | 32 | Week 1 33 | * What is Reactive Programming? (11:42) 34 | * Recap: Functions and Pattern Matching (19:56) 35 | * Recap: Collections (12:54) 36 | * Functional Random Generators (19:42) 37 | * Monads (20:22) 38 | 39 | Week 2 40 | * Functions and State (15:28) 41 | * Identity and Change (8:12) 42 | * Loops (8:25) 43 | * Extended Example: Discrete Event Simulation (Optional) (10:54) 44 | * Discrete Event Simulation: API and Usage (Optional) (10:57) 45 | * Discrete Event Simluation: Implementation and Test (Optional) (18:12) 46 | * Imperative Event Handling: The Observer Pattern (12:27) 47 | * Functional Reactive Programming (20:24) 48 | * A Simple FRP Implementation (19:32) 49 | 50 | Week 3 51 | * Monads and Effects 1 52 | * Monads and Effects 2 53 | * Latency as an Effect 1 54 | * Latency as an Effect 2 55 | * Combinators on Futures 1 56 | * Combinators on Futures 2 57 | * Composing Futures 1 58 | * Composing Futures 2 59 | * Async Await 60 | * Promises, promises 61 | 62 | Week 4 63 | * From Try to Future (5:22) 64 | * From Iterables to Observables 1 (8:06) 65 | * From Iterables to Observables 2 (9:44) 66 | * Hello World Observables (6:29) 67 | * RX Operators (11:39) 68 | * Subscriptions (10:34) 69 | * Promises and Subjects (8:55) 70 | * RX potpourri (11:30) 71 | * Observable Contract (14:19) 72 | 73 | Week 5 74 | * Introduction: Why Actors? (14:46) 75 | * The Actor Model (13:43) 76 | * Message Processing Semantics (27:28) 77 | * Designing Actor Systems (38:10) 78 | * Testing Actor Systems (17:16) 79 | 80 | Week 6 81 | * Failure Handling with Actors (22:38) 82 | * Lifecycle Monitoring and the Error Kernel (24:07) 83 | * Persistent Actor State (40:05) 84 | 85 | Week 7 86 | * Actors are Distributed (36:30) 87 | * Actors are Distributed Part II (18:17 — optional) 88 | * Eventual Consistency (15:49) 89 | * Actor Composition (20:14) 90 | * Scalability (17:00) 91 | * Responsiveness (11:41) 92 | -------------------------------------------------------------------------------- /ReactiveCheatSheet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasnake/Principles-of-Reactive-Programming/66b432473a81342569394c331fc092b8f39505de/ReactiveCheatSheet.pdf -------------------------------------------------------------------------------- /akka-cluster-sharding-scala.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasnake/Principles-of-Reactive-Programming/66b432473a81342569394c331fc092b8f39505de/akka-cluster-sharding-scala.zip -------------------------------------------------------------------------------- /reactive-course source code-reactive-week1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasnake/Principles-of-Reactive-Programming/66b432473a81342569394c331fc092b8f39505de/reactive-course source code-reactive-week1.zip -------------------------------------------------------------------------------- /reactive-course source code-reactive-week2.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasnake/Principles-of-Reactive-Programming/66b432473a81342569394c331fc092b8f39505de/reactive-course source code-reactive-week2.zip -------------------------------------------------------------------------------- /reactive.scala.learning-resources.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasnake/Principles-of-Reactive-Programming/66b432473a81342569394c331fc092b8f39505de/reactive.scala.learning-resources.pdf -------------------------------------------------------------------------------- /w1/quickcheck/build.sbt: -------------------------------------------------------------------------------- 1 | submitProjectName := "quickcheck" 2 | 3 | scalaVersion := "2.11.5" 4 | 5 | scalacOptions ++= Seq("-deprecation", "-feature") 6 | 7 | (fork in Test) := false 8 | 9 | projectDetailsMap := { 10 | val currentCourseId = "reactive-002" 11 | 12 | val depsNode = Seq( 13 | "com.netflix.rxjava" % "rxjava-scala" % "0.15.0", 14 | "org.json4s" %% "json4s-native" % "3.2.11", 15 | "org.scala-lang.modules" %% "scala-swing" % "1.0.1", 16 | "net.databinder.dispatch" %% "dispatch-core" % "0.11.0", 17 | "org.scala-lang" % "scala-reflect" % scalaVersion.value, 18 | "org.slf4j" % "slf4j-api" % "1.7.5", 19 | "org.slf4j" % "slf4j-simple" % "1.7.5", 20 | "com.squareup.retrofit" % "retrofit" % "1.0.0", 21 | "org.scala-lang.modules" %% "scala-async" % "0.9.2" 22 | ) 23 | 24 | val depsAkka = Seq( 25 | "com.typesafe.akka" %% "akka-actor" % "2.3.9", 26 | "com.typesafe.akka" %% "akka-testkit" % "2.3.9", 27 | "com.typesafe.akka" %% "akka-persistence-experimental" % "2.3.9" 28 | ) 29 | 30 | Map( 31 | "example" -> ProjectDetails( 32 | packageName = "example", 33 | assignmentPartId = "fTzFogNl", 34 | maxScore = 10d, 35 | styleScoreRatio = 0.0, 36 | courseId=currentCourseId), 37 | "quickcheck" -> ProjectDetails( 38 | packageName = "quickcheck", 39 | assignmentPartId = "02Vi5q7m", 40 | maxScore = 10d, 41 | styleScoreRatio = 0.0, 42 | courseId=currentCourseId, 43 | dependencies = Seq("org.scalacheck" %% "scalacheck" % "1.12.1")), 44 | "simulations" -> ProjectDetails( 45 | packageName = "simulations", 46 | assignmentPartId = "pA3TAeu1", 47 | maxScore = 10d, 48 | styleScoreRatio = 0.0, 49 | courseId=currentCourseId), 50 | "nodescala" -> ProjectDetails( 51 | packageName = "nodescala", 52 | assignmentPartId = "RvoTAbRy", 53 | maxScore = 10d, 54 | styleScoreRatio = 0.0, 55 | courseId=currentCourseId, 56 | dependencies = depsNode), 57 | "suggestions" -> ProjectDetails( 58 | packageName = "suggestions", 59 | assignmentPartId = "rLLdQLGN", 60 | maxScore = 10d, 61 | styleScoreRatio = 0.0, 62 | courseId=currentCourseId), 63 | "actorbintree" -> ProjectDetails( 64 | packageName = "actorbintree", 65 | assignmentPartId = "VxIlIKoW", 66 | maxScore = 10d, 67 | styleScoreRatio = 0.0, 68 | courseId=currentCourseId, 69 | dependencies = depsAkka), 70 | "kvstore" -> ProjectDetails( 71 | packageName = "kvstore", 72 | assignmentPartId = "nuvh59Zi", 73 | maxScore = 20d, 74 | styleScoreRatio = 0.0, 75 | courseId=currentCourseId, 76 | dependencies = depsAkka) 77 | )} 78 | -------------------------------------------------------------------------------- /w1/quickcheck/project/ReactiveBuild.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | import ch.epfl.lamp.CourseraBuild 4 | import ch.epfl.lamp.SbtCourseraPlugin.autoImport._ 5 | 6 | object ProgfunBuild extends CourseraBuild { 7 | override def assignmentSettings: Seq[Setting[_]] = Seq( 8 | // This setting allows to restrict the source files that are compiled and tested 9 | // to one specific project. It should be either the empty string, in which case all 10 | // projects are included, or one of the project names from the projectDetailsMap. 11 | currentProject := "", 12 | 13 | // Packages in src/main/scala that are used in every project. Included in every 14 | // handout, submission. 15 | commonSourcePackages += "common", 16 | 17 | // Packages in src/test/scala that are used for grading projects. Always included 18 | // compiling tests, grading a project. 19 | 20 | libraryDependencies += "ch.epfl.lamp" %% "scala-grading-runtime" % "0.1", 21 | 22 | // Files that we hand out to the students 23 | handoutFiles <<= (baseDirectory, projectDetailsMap, commonSourcePackages) map { 24 | (basedir, detailsMap, commonSrcs) => 25 | (projectName: String) => { 26 | val details = detailsMap.getOrElse(projectName, sys.error("Unknown project name: "+ projectName)) 27 | val commonFiles = (PathFinder.empty /: commonSrcs)((files, pkg) => 28 | files +++ (basedir / "src" / "main" / "scala" / pkg ** "*.scala") 29 | ) 30 | (basedir / "src" / "main" / "scala" / details.packageName ** "*.scala") +++ 31 | commonFiles +++ 32 | (basedir / "src" / "main" / "resources" / details.packageName / "*") +++ 33 | (basedir / "src" / "test" / "scala" / details.packageName ** "*.scala") +++ 34 | (basedir / "build.sbt") +++ 35 | (basedir / "project" / "build.properties") +++ 36 | (basedir / "project" ** ("*.scala" || "*.sbt")) +++ 37 | (basedir / "project" / "scalastyle_config_reactive.xml") +++ 38 | (basedir / "lib_managed" ** "*.jar") +++ 39 | (basedir * (".classpath" || ".project")) +++ 40 | (basedir / ".settings" / "org.scala-ide.sdt.core.prefs") 41 | } 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /w1/quickcheck/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /w1/quickcheck/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("ch.epfl.lamp" % "sbt-coursera" % "0.5") 2 | -------------------------------------------------------------------------------- /w1/quickcheck/src/main/scala/common/package.scala: -------------------------------------------------------------------------------- 1 | import java.io.File 2 | 3 | package object common { 4 | 5 | /** An alias for the `Nothing` type. 6 | * Denotes that the type should be filled in. 7 | */ 8 | type ??? = Nothing 9 | 10 | /** An alias for the `Any` type. 11 | * Denotes that the type should be filled in. 12 | */ 13 | type *** = Any 14 | 15 | 16 | /** 17 | * Get a child of a file. For example, 18 | * 19 | * subFile(homeDir, "b", "c") 20 | * 21 | * corresponds to ~/b/c 22 | */ 23 | def subFile(file: File, children: String*) = { 24 | children.foldLeft(file)((file, child) => new File(file, child)) 25 | } 26 | 27 | /** 28 | * Get a resource from the `src/main/resources` directory. Eclipse does not copy 29 | * resources to the output directory, then the class loader cannot find them. 30 | */ 31 | def resourceAsStreamFromSrc(resourcePath: List[String]): Option[java.io.InputStream] = { 32 | val classesDir = new File(getClass.getResource(".").toURI) 33 | val projectDir = classesDir.getParentFile.getParentFile.getParentFile.getParentFile 34 | val resourceFile = subFile(projectDir, ("src" :: "main" :: "resources" :: resourcePath): _*) 35 | if (resourceFile.exists) 36 | Some(new java.io.FileInputStream(resourceFile)) 37 | else 38 | None 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /w1/quickcheck/src/main/scala/quickcheck/Heap.scala: -------------------------------------------------------------------------------- 1 | package quickcheck 2 | 3 | import common._ 4 | 5 | trait IntHeap extends Heap { 6 | override type A = Int 7 | override def ord = scala.math.Ordering.Int 8 | } 9 | 10 | // http://www.brics.dk/RS/96/37/BRICS-RS-96-37.pdf 11 | 12 | // Figure 1, page 3 13 | trait Heap { 14 | type H // type of a heap 15 | type A // type of an element 16 | def ord: Ordering[A] // ordering on elements 17 | 18 | def empty: H // the empty heap 19 | def isEmpty(h: H): Boolean // whether the given heap h is empty 20 | 21 | def insert(x: A, h: H): H // the heap resulting from inserting x into h 22 | def meld(h1: H, h2: H): H // the heap resulting from merging h1 and h2 23 | 24 | def findMin(h: H): A // a minimum of the heap h 25 | def deleteMin(h: H): H // a heap resulting from deleting a minimum of h 26 | } 27 | 28 | // Figure 3, page 7 29 | trait BinomialHeap extends Heap { 30 | 31 | type Rank = Int 32 | case class Node(x: A, r: Rank, c: List[Node]) 33 | override type H = List[Node] 34 | 35 | protected def root(t: Node) = t.x 36 | protected def rank(t: Node) = t.r 37 | protected def link(t1: Node, t2: Node): Node = // t1.r==t2.r 38 | if (ord.lteq(t1.x,t2.x)) Node(t1.x, t1.r+1, t2::t1.c) else Node(t2.x, t2.r+1, t1::t2.c) 39 | protected def ins(t: Node, ts: H): H = ts match { 40 | case Nil => List(t) 41 | case tp::ts => // t.r<=tp.r 42 | if (t.r ts 51 | case (ts, Nil) => ts 52 | case (t1::ts1, t2::ts2) => 53 | if (t1.r throw new NoSuchElementException("min of empty heap") 60 | case t::Nil => root(t) 61 | case t::ts => 62 | val x = findMin(ts) 63 | if (ord.lteq(root(t),x)) root(t) else x 64 | } 65 | override def deleteMin(ts: H) = ts match { 66 | case Nil => throw new NoSuchElementException("delete min of empty heap") 67 | case t::ts => 68 | def getMin(t: Node, ts: H): (Node, H) = ts match { 69 | case Nil => (t, Nil) 70 | case tp::tsp => 71 | val (tq,tsq) = getMin(tp, tsp) 72 | if (ord.lteq(root(t),root(tq))) (t,ts) else (tq,t::tsq) 73 | } 74 | val (Node(_,_,c),tsq) = getMin(t, ts) 75 | meld(c.reverse, tsq) 76 | } 77 | } 78 | 79 | trait Bogus1BinomialHeap extends BinomialHeap { 80 | override def findMin(ts: H) = ts match { 81 | case Nil => throw new NoSuchElementException("min of empty heap") 82 | case t::ts => root(t) 83 | } 84 | } 85 | 86 | trait Bogus2BinomialHeap extends BinomialHeap { 87 | override protected def link(t1: Node, t2: Node): Node = // t1.r==t2.r 88 | if (!ord.lteq(t1.x,t2.x)) Node(t1.x, t1.r+1, t2::t1.c) else Node(t2.x, t2.r+1, t1::t2.c) 89 | } 90 | 91 | trait Bogus3BinomialHeap extends BinomialHeap { 92 | override protected def link(t1: Node, t2: Node): Node = // t1.r==t2.r 93 | if (ord.lteq(t1.x,t2.x)) Node(t1.x, t1.r+1, t1::t1.c) else Node(t2.x, t2.r+1, t2::t2.c) 94 | } 95 | 96 | trait Bogus4BinomialHeap extends BinomialHeap { 97 | override def deleteMin(ts: H) = ts match { 98 | case Nil => throw new NoSuchElementException("delete min of empty heap") 99 | case t::ts => meld(t.c.reverse, ts) 100 | } 101 | } 102 | 103 | trait Bogus5BinomialHeap extends BinomialHeap { 104 | override def meld(ts1: H, ts2: H) = ts1 match { 105 | case Nil => ts2 106 | case t1::ts1 => List(Node(t1.x, t1.r, ts1++ts2)) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /w1/quickcheck/src/main/scala/quickcheck/QuickCheck.scala: -------------------------------------------------------------------------------- 1 | package quickcheck 2 | 3 | import common._ 4 | 5 | import org.scalacheck._ 6 | import Arbitrary._ 7 | import Gen._ 8 | import Prop._ 9 | 10 | abstract class QuickCheckHeap extends Properties("Heap") with IntHeap { 11 | 12 | property("min1") = forAll { a: Int => 13 | val h = insert(a, empty) 14 | findMin(h) == a 15 | } 16 | 17 | lazy val genHeap: Gen[H] = ??? 18 | 19 | implicit lazy val arbHeap: Arbitrary[H] = Arbitrary(genHeap) 20 | 21 | } 22 | -------------------------------------------------------------------------------- /w1/quickcheck/src/test/scala/quickcheck/QuickCheckSuite.scala: -------------------------------------------------------------------------------- 1 | package quickcheck 2 | 3 | import org.scalatest.FunSuite 4 | 5 | import org.junit.runner.RunWith 6 | import org.scalatest.junit.JUnitRunner 7 | 8 | import org.scalatest.prop.Checkers 9 | import org.scalacheck.Arbitrary._ 10 | import org.scalacheck.Prop 11 | import org.scalacheck.Prop._ 12 | 13 | import org.scalatest.exceptions.TestFailedException 14 | 15 | object QuickCheckBinomialHeap extends QuickCheckHeap with BinomialHeap 16 | 17 | @RunWith(classOf[JUnitRunner]) 18 | class QuickCheckSuite extends FunSuite with Checkers { 19 | def checkBogus(p: Prop) { 20 | var ok = false 21 | try { 22 | check(p) 23 | } catch { 24 | case e: TestFailedException => 25 | ok = true 26 | } 27 | assert(ok, "A bogus heap should NOT satisfy all properties. Try to find the bug!") 28 | } 29 | 30 | test("Binomial heap satisfies properties.") { 31 | check(new QuickCheckHeap with BinomialHeap) 32 | } 33 | 34 | test("Bogus (1) binomial heap does not satisfy properties.") { 35 | checkBogus(new QuickCheckHeap with Bogus1BinomialHeap) 36 | } 37 | 38 | test("Bogus (2) binomial heap does not satisfy properties.") { 39 | checkBogus(new QuickCheckHeap with Bogus2BinomialHeap) 40 | } 41 | 42 | test("Bogus (3) binomial heap does not satisfy properties.") { 43 | checkBogus(new QuickCheckHeap with Bogus3BinomialHeap) 44 | } 45 | 46 | test("Bogus (4) binomial heap does not satisfy properties.") { 47 | checkBogus(new QuickCheckHeap with Bogus4BinomialHeap) 48 | } 49 | 50 | test("Bogus (5) binomial heap does not satisfy properties.") { 51 | checkBogus(new QuickCheckHeap with Bogus5BinomialHeap) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /w1/week1.quickcheck-instructions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasnake/Principles-of-Reactive-Programming/66b432473a81342569394c331fc092b8f39505de/w1/week1.quickcheck-instructions.pdf -------------------------------------------------------------------------------- /w2/calculator/build.sbt: -------------------------------------------------------------------------------- 1 | submitProjectName := "calculator" 2 | 3 | scalaVersion := "2.11.5" 4 | 5 | scalacOptions ++= Seq("-deprecation", "-feature") 6 | 7 | (fork in Test) := false 8 | 9 | projectDetailsMap := { 10 | val currentCourseId = "reactive-002" 11 | 12 | val depsNode = Seq( 13 | "com.netflix.rxjava" % "rxjava-scala" % "0.15.0", 14 | "org.json4s" %% "json4s-native" % "3.2.11", 15 | "org.scala-lang.modules" %% "scala-swing" % "1.0.1", 16 | "net.databinder.dispatch" %% "dispatch-core" % "0.11.0", 17 | "org.scala-lang" % "scala-reflect" % scalaVersion.value, 18 | "org.slf4j" % "slf4j-api" % "1.7.5", 19 | "org.slf4j" % "slf4j-simple" % "1.7.5", 20 | "com.squareup.retrofit" % "retrofit" % "1.0.0", 21 | "org.scala-lang.modules" %% "scala-async" % "0.9.2" 22 | ) 23 | 24 | val depsAkka = Seq( 25 | "com.typesafe.akka" %% "akka-actor" % "2.3.9", 26 | "com.typesafe.akka" %% "akka-testkit" % "2.3.9", 27 | "com.typesafe.akka" %% "akka-persistence-experimental" % "2.3.9" 28 | ) 29 | 30 | Map( 31 | "example" -> ProjectDetails( 32 | packageName = "example", 33 | assignmentPartId = "fTzFogNl", 34 | maxScore = 10d, 35 | styleScoreRatio = 0.0, 36 | courseId=currentCourseId), 37 | "quickcheck" -> ProjectDetails( 38 | packageName = "quickcheck", 39 | assignmentPartId = "02Vi5q7m", 40 | maxScore = 10d, 41 | styleScoreRatio = 0.0, 42 | courseId=currentCourseId, 43 | dependencies = Seq("org.scalacheck" %% "scalacheck" % "1.12.1")), 44 | "calculator" -> ProjectDetails( 45 | packageName = "calculator", 46 | assignmentPartId = "8uURtbi7", 47 | maxScore = 10d, 48 | styleScoreRatio = 0.0, 49 | courseId=currentCourseId), 50 | "nodescala" -> ProjectDetails( 51 | packageName = "nodescala", 52 | assignmentPartId = "RvoTAbRy", 53 | maxScore = 10d, 54 | styleScoreRatio = 0.0, 55 | courseId=currentCourseId, 56 | dependencies = depsNode), 57 | "suggestions" -> ProjectDetails( 58 | packageName = "suggestions", 59 | assignmentPartId = "rLLdQLGN", 60 | maxScore = 10d, 61 | styleScoreRatio = 0.0, 62 | courseId=currentCourseId), 63 | "actorbintree" -> ProjectDetails( 64 | packageName = "actorbintree", 65 | assignmentPartId = "VxIlIKoW", 66 | maxScore = 10d, 67 | styleScoreRatio = 0.0, 68 | courseId=currentCourseId, 69 | dependencies = depsAkka), 70 | "kvstore" -> ProjectDetails( 71 | packageName = "kvstore", 72 | assignmentPartId = "nuvh59Zi", 73 | maxScore = 20d, 74 | styleScoreRatio = 0.0, 75 | courseId=currentCourseId, 76 | dependencies = depsAkka) 77 | )} 78 | -------------------------------------------------------------------------------- /w2/calculator/project/ReactiveBuild.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | import ch.epfl.lamp.CourseraBuild 4 | import ch.epfl.lamp.SbtCourseraPlugin.autoImport._ 5 | 6 | import org.scalajs.sbtplugin.ScalaJSPlugin 7 | import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ 8 | 9 | object ProgfunBuild extends CourseraBuild { 10 | override def assignmentSettings: Seq[Setting[_]] = Seq( 11 | // This setting allows to restrict the source files that are compiled and tested 12 | // to one specific project. It should be either the empty string, in which case all 13 | // projects are included, or one of the project names from the projectDetailsMap. 14 | currentProject := "", 15 | 16 | // Packages in src/main/scala that are used in every project. Included in every 17 | // handout, submission. 18 | commonSourcePackages += "common", 19 | 20 | // Packages in src/test/scala that are used for grading projects. Always included 21 | // compiling tests, grading a project. 22 | 23 | libraryDependencies += "ch.epfl.lamp" %% "scala-grading-runtime" % "0.1", 24 | 25 | // Files that we hand out to the students 26 | handoutFiles <<= (baseDirectory, projectDetailsMap, commonSourcePackages) map { 27 | (basedir, detailsMap, commonSrcs) => 28 | (projectName: String) => { 29 | val details = detailsMap.getOrElse(projectName, sys.error("Unknown project name: "+ projectName)) 30 | val commonFiles = (PathFinder.empty /: commonSrcs)((files, pkg) => 31 | files +++ (basedir / "src" / "main" / "scala" / pkg ** "*.scala") 32 | ) 33 | val forAll = { 34 | (basedir / "src" / "main" / "scala" / details.packageName ** "*.scala") +++ 35 | commonFiles +++ 36 | (basedir / "src" / "main" / "resources" / details.packageName / "*") +++ 37 | (basedir / "src" / "test" / "scala" / details.packageName ** "*.scala") +++ 38 | (basedir / "build.sbt") +++ 39 | (basedir / "project" / "build.properties") +++ 40 | (basedir / "project" ** ("*.scala" || "*.sbt")) +++ 41 | (basedir / "project" / "scalastyle_config_reactive.xml") +++ 42 | (basedir / "lib_managed" ** "*.jar") +++ 43 | (basedir * (".classpath" || ".project")) +++ 44 | (basedir / ".settings" / "org.scala-ide.sdt.core.prefs") 45 | } 46 | if (projectName == "calculator") { 47 | forAll +++ 48 | (basedir / "webui.sbt") +++ 49 | (basedir / "web-ui" / "index.html") +++ 50 | (basedir / "web-ui" / "src" / "main" / "scala" ** "*.scala") 51 | } else 52 | forAll 53 | } 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /w2/calculator/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /w2/calculator/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("ch.epfl.lamp" % "sbt-coursera" % "0.5") 2 | 3 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.2") 4 | -------------------------------------------------------------------------------- /w2/calculator/src/main/scala/calculator/Calculator.scala: -------------------------------------------------------------------------------- 1 | package calculator 2 | 3 | sealed abstract class Expr 4 | final case class Literal(v: Double) extends Expr 5 | final case class Ref(name: String) extends Expr 6 | final case class Plus(a: Expr, b: Expr) extends Expr 7 | final case class Minus(a: Expr, b: Expr) extends Expr 8 | final case class Times(a: Expr, b: Expr) extends Expr 9 | final case class Divide(a: Expr, b: Expr) extends Expr 10 | 11 | object Calculator { 12 | def computeValues( 13 | namedExpressions: Map[String, Signal[Expr]]): Map[String, Signal[Double]] = { 14 | ??? 15 | } 16 | 17 | def eval(expr: Expr, references: Map[String, Signal[Expr]]): Double = { 18 | ??? 19 | } 20 | 21 | /** Get the Expr for a referenced variables. 22 | * If the variable is not known, returns a literal NaN. 23 | */ 24 | private def getReferenceExpr(name: String, 25 | references: Map[String, Signal[Expr]]) = { 26 | references.get(name).fold[Expr] { 27 | Literal(Double.NaN) 28 | } { exprSignal => 29 | exprSignal() 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /w2/calculator/src/main/scala/calculator/Polynomial.scala: -------------------------------------------------------------------------------- 1 | package calculator 2 | 3 | object Polynomial { 4 | def computeDelta(a: Signal[Double], b: Signal[Double], 5 | c: Signal[Double]): Signal[Double] = { 6 | ??? 7 | } 8 | 9 | def computeSolutions(a: Signal[Double], b: Signal[Double], 10 | c: Signal[Double], delta: Signal[Double]): Signal[Set[Double]] = { 11 | ??? 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /w2/calculator/src/main/scala/calculator/Signal.scala: -------------------------------------------------------------------------------- 1 | package calculator 2 | 3 | import scala.util.DynamicVariable 4 | 5 | class Signal[T](expr: => T) { 6 | import Signal._ 7 | private var myExpr: () => T = _ 8 | private var myValue: T = _ 9 | private var observers: Set[Signal[_]] = Set() 10 | private var observed: List[Signal[_]] = Nil 11 | update(expr) 12 | 13 | protected def computeValue(): Unit = { 14 | for (sig <- observed) 15 | sig.observers -= this 16 | observed = Nil 17 | val newValue = caller.withValue(this)(myExpr()) 18 | /* Disable the following "optimization" for the assignment, because we 19 | * want to be able to track the actual dependency graph in the tests. 20 | */ 21 | //if (myValue != newValue) { 22 | myValue = newValue 23 | val obs = observers 24 | observers = Set() 25 | obs.foreach(_.computeValue()) 26 | //} 27 | } 28 | 29 | protected def update(expr: => T): Unit = { 30 | myExpr = () => expr 31 | computeValue() 32 | } 33 | 34 | def apply() = { 35 | observers += caller.value 36 | assert(!caller.value.observers.contains(this), "cyclic signal definition") 37 | caller.value.observed ::= this 38 | myValue 39 | } 40 | } 41 | 42 | class Var[T](expr: => T) extends Signal[T](expr) { 43 | override def update(expr: => T): Unit = super.update(expr) 44 | } 45 | 46 | object Var { 47 | def apply[T](expr: => T) = new Var(expr) 48 | } 49 | 50 | object NoSignal extends Signal[Nothing](???) { 51 | override def computeValue() = () 52 | } 53 | 54 | object Signal { 55 | val caller = new DynamicVariable[Signal[_]](NoSignal) 56 | def apply[T](expr: => T) = new Signal(expr) 57 | } 58 | -------------------------------------------------------------------------------- /w2/calculator/src/main/scala/calculator/TweetLength.scala: -------------------------------------------------------------------------------- 1 | package calculator 2 | 3 | object TweetLength { 4 | final val MaxTweetLength = 140 5 | 6 | def tweetRemainingCharsCount(tweetText: Signal[String]): Signal[Int] = { 7 | ??? 8 | } 9 | 10 | def colorForRemainingCharsCount(remainingCharsCount: Signal[Int]): Signal[String] = { 11 | ??? 12 | } 13 | 14 | /** Computes the length of a tweet, given its text string. 15 | * This is not equivalent to text.length, as tweet lengths count the number 16 | * of Unicode *code points* in the string. 17 | * Note that this is still a simplified view of the reality. Full details 18 | * can be found at 19 | * https://dev.twitter.com/overview/api/counting-characters 20 | */ 21 | private def tweetLength(text: String): Int = { 22 | /* This should be simply text.codePointCount(0, text.length), but it 23 | * is not implemented in Scala.js 0.6.2. 24 | */ 25 | if (text.isEmpty) 0 26 | else { 27 | text.length - text.init.zip(text.tail).count( 28 | (Character.isSurrogatePair _).tupled) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /w2/calculator/src/main/scala/common/package.scala: -------------------------------------------------------------------------------- 1 | import java.io.File 2 | 3 | package object common { 4 | 5 | /** An alias for the `Nothing` type. 6 | * Denotes that the type should be filled in. 7 | */ 8 | type ??? = Nothing 9 | 10 | /** An alias for the `Any` type. 11 | * Denotes that the type should be filled in. 12 | */ 13 | type *** = Any 14 | 15 | 16 | /** 17 | * Get a child of a file. For example, 18 | * 19 | * subFile(homeDir, "b", "c") 20 | * 21 | * corresponds to ~/b/c 22 | */ 23 | def subFile(file: File, children: String*) = { 24 | children.foldLeft(file)((file, child) => new File(file, child)) 25 | } 26 | 27 | /** 28 | * Get a resource from the `src/main/resources` directory. Eclipse does not copy 29 | * resources to the output directory, then the class loader cannot find them. 30 | */ 31 | def resourceAsStreamFromSrc(resourcePath: List[String]): Option[java.io.InputStream] = { 32 | val classesDir = new File(getClass.getResource(".").toURI) 33 | val projectDir = classesDir.getParentFile.getParentFile.getParentFile.getParentFile 34 | val resourceFile = subFile(projectDir, ("src" :: "main" :: "resources" :: resourcePath): _*) 35 | if (resourceFile.exists) 36 | Some(new java.io.FileInputStream(resourceFile)) 37 | else 38 | None 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /w2/calculator/src/test/scala/calculator/CalculatorSuite.scala: -------------------------------------------------------------------------------- 1 | package calculator 2 | 3 | import org.scalatest.FunSuite 4 | 5 | import org.junit.runner.RunWith 6 | import org.scalatest.junit.JUnitRunner 7 | 8 | import org.scalatest._ 9 | 10 | import TweetLength.MaxTweetLength 11 | 12 | @RunWith(classOf[JUnitRunner]) 13 | class CalculatorSuite extends FunSuite with ShouldMatchers { 14 | 15 | /****************** 16 | ** TWEET LENGTH ** 17 | ******************/ 18 | 19 | def tweetLength(text: String): Int = 20 | text.codePointCount(0, text.length) 21 | 22 | test("tweetRemainingCharsCount with a constant signal") { 23 | val result = TweetLength.tweetRemainingCharsCount(Var("hello world")) 24 | assert(result() == MaxTweetLength - tweetLength("hello world")) 25 | 26 | val tooLong = "foo" * 200 27 | val result2 = TweetLength.tweetRemainingCharsCount(Var(tooLong)) 28 | assert(result2() == MaxTweetLength - tweetLength(tooLong)) 29 | } 30 | 31 | test("tweetRemainingCharsCount with a supplementary char") { 32 | val result = TweetLength.tweetRemainingCharsCount(Var("foo blabla \uD83D\uDCA9 bar")) 33 | assert(result() == MaxTweetLength - tweetLength("foo blabla \uD83D\uDCA9 bar")) 34 | } 35 | 36 | 37 | test("colorForRemainingCharsCount with a constant signal") { 38 | val resultGreen1 = TweetLength.colorForRemainingCharsCount(Var(52)) 39 | assert(resultGreen1() == "green") 40 | val resultGreen2 = TweetLength.colorForRemainingCharsCount(Var(15)) 41 | assert(resultGreen2() == "green") 42 | 43 | val resultOrange1 = TweetLength.colorForRemainingCharsCount(Var(12)) 44 | assert(resultOrange1() == "orange") 45 | val resultOrange2 = TweetLength.colorForRemainingCharsCount(Var(0)) 46 | assert(resultOrange2() == "orange") 47 | 48 | val resultRed1 = TweetLength.colorForRemainingCharsCount(Var(-1)) 49 | assert(resultRed1() == "red") 50 | val resultRed2 = TweetLength.colorForRemainingCharsCount(Var(-5)) 51 | assert(resultRed2() == "red") 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /w2/calculator/webui.sbt: -------------------------------------------------------------------------------- 1 | lazy val webUI = project.in(file("web-ui")). 2 | enablePlugins(ScalaJSPlugin). 3 | settings( 4 | scalaVersion := "2.11.6", 5 | // Add the sources of the calculator project 6 | unmanagedSourceDirectories in Compile += 7 | (scalaSource in (assignmentProject, Compile)).value / "calculator", 8 | libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "0.8.0", 9 | persistLauncher := true 10 | ) 11 | -------------------------------------------------------------------------------- /w2/week2.calculator-instructions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasnake/Principles-of-Reactive-Programming/66b432473a81342569394c331fc092b8f39505de/w2/week2.calculator-instructions.pdf -------------------------------------------------------------------------------- /w3/nodescala/build.sbt: -------------------------------------------------------------------------------- 1 | submitProjectName := "nodescala" 2 | 3 | scalaVersion := "2.11.5" 4 | 5 | scalacOptions ++= Seq("-deprecation", "-feature") 6 | 7 | (fork in Test) := false 8 | 9 | projectDetailsMap := { 10 | val currentCourseId = "reactive-002" 11 | 12 | val depsNode = Seq( 13 | "com.netflix.rxjava" % "rxjava-scala" % "0.15.0", 14 | "org.json4s" %% "json4s-native" % "3.2.11", 15 | "org.scala-lang.modules" %% "scala-swing" % "1.0.1", 16 | "net.databinder.dispatch" %% "dispatch-core" % "0.11.0", 17 | "org.scala-lang" % "scala-reflect" % scalaVersion.value, 18 | "org.slf4j" % "slf4j-api" % "1.7.5", 19 | "org.slf4j" % "slf4j-simple" % "1.7.5", 20 | "com.squareup.retrofit" % "retrofit" % "1.0.0", 21 | "org.scala-lang.modules" %% "scala-async" % "0.9.2" 22 | ) 23 | 24 | val depsAkka = Seq( 25 | "com.typesafe.akka" %% "akka-actor" % "2.3.9", 26 | "com.typesafe.akka" %% "akka-testkit" % "2.3.9", 27 | "com.typesafe.akka" %% "akka-persistence-experimental" % "2.3.9" 28 | ) 29 | 30 | Map( 31 | "example" -> ProjectDetails( 32 | packageName = "example", 33 | assignmentPartId = "fTzFogNl", 34 | maxScore = 10d, 35 | styleScoreRatio = 0.0, 36 | courseId=currentCourseId), 37 | "quickcheck" -> ProjectDetails( 38 | packageName = "quickcheck", 39 | assignmentPartId = "02Vi5q7m", 40 | maxScore = 10d, 41 | styleScoreRatio = 0.0, 42 | courseId=currentCourseId, 43 | dependencies = Seq("org.scalacheck" %% "scalacheck" % "1.12.1")), 44 | "calculator" -> ProjectDetails( 45 | packageName = "calculator", 46 | assignmentPartId = "8uURtbi7", 47 | maxScore = 10d, 48 | styleScoreRatio = 0.0, 49 | courseId=currentCourseId), 50 | "nodescala" -> ProjectDetails( 51 | packageName = "nodescala", 52 | assignmentPartId = "RvoTAbRy", 53 | maxScore = 10d, 54 | styleScoreRatio = 0.0, 55 | courseId=currentCourseId, 56 | dependencies = depsNode), 57 | "suggestions" -> ProjectDetails( 58 | packageName = "suggestions", 59 | assignmentPartId = "rLLdQLGN", 60 | maxScore = 10d, 61 | styleScoreRatio = 0.0, 62 | courseId=currentCourseId), 63 | "actorbintree" -> ProjectDetails( 64 | packageName = "actorbintree", 65 | assignmentPartId = "VxIlIKoW", 66 | maxScore = 10d, 67 | styleScoreRatio = 0.0, 68 | courseId=currentCourseId, 69 | dependencies = depsAkka), 70 | "kvstore" -> ProjectDetails( 71 | packageName = "kvstore", 72 | assignmentPartId = "nuvh59Zi", 73 | maxScore = 20d, 74 | styleScoreRatio = 0.0, 75 | courseId=currentCourseId, 76 | dependencies = depsAkka) 77 | )} 78 | -------------------------------------------------------------------------------- /w3/nodescala/project/ReactiveBuild.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | import ch.epfl.lamp.CourseraBuild 4 | import ch.epfl.lamp.SbtCourseraPlugin.autoImport._ 5 | 6 | import org.scalajs.sbtplugin.ScalaJSPlugin 7 | import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ 8 | 9 | object ProgfunBuild extends CourseraBuild { 10 | override def assignmentSettings: Seq[Setting[_]] = Seq( 11 | // This setting allows to restrict the source files that are compiled and tested 12 | // to one specific project. It should be either the empty string, in which case all 13 | // projects are included, or one of the project names from the projectDetailsMap. 14 | currentProject := "", 15 | 16 | // Packages in src/main/scala that are used in every project. Included in every 17 | // handout, submission. 18 | commonSourcePackages += "common", 19 | 20 | // Packages in src/test/scala that are used for grading projects. Always included 21 | // compiling tests, grading a project. 22 | 23 | libraryDependencies += "ch.epfl.lamp" %% "scala-grading-runtime" % "0.1", 24 | 25 | // Files that we hand out to the students 26 | handoutFiles <<= (baseDirectory, projectDetailsMap, commonSourcePackages) map { 27 | (basedir, detailsMap, commonSrcs) => 28 | (projectName: String) => { 29 | val details = detailsMap.getOrElse(projectName, sys.error("Unknown project name: "+ projectName)) 30 | val commonFiles = (PathFinder.empty /: commonSrcs)((files, pkg) => 31 | files +++ (basedir / "src" / "main" / "scala" / pkg ** "*.scala") 32 | ) 33 | val forAll = { 34 | (basedir / "src" / "main" / "scala" / details.packageName ** "*.scala") +++ 35 | commonFiles +++ 36 | (basedir / "src" / "main" / "resources" / details.packageName / "*") +++ 37 | (basedir / "src" / "test" / "scala" / details.packageName ** "*.scala") +++ 38 | (basedir / "build.sbt") +++ 39 | (basedir / "project" / "build.properties") +++ 40 | (basedir / "project" ** ("*.scala" || "*.sbt")) +++ 41 | (basedir / "project" / "scalastyle_config_reactive.xml") +++ 42 | (basedir / "lib_managed" ** "*.jar") +++ 43 | (basedir * (".classpath" || ".project")) +++ 44 | (basedir / ".settings" / "org.scala-ide.sdt.core.prefs") 45 | } 46 | if (projectName == "calculator") { 47 | forAll +++ 48 | (basedir / "webui.sbt") +++ 49 | (basedir / "web-ui" / "index.html") +++ 50 | (basedir / "web-ui" / "src" / "main" / "scala" ** "*.scala") 51 | } else 52 | forAll 53 | } 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /w3/nodescala/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /w3/nodescala/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("ch.epfl.lamp" % "sbt-coursera" % "0.5") 2 | 3 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.2") 4 | -------------------------------------------------------------------------------- /w3/nodescala/src/main/scala/common/package.scala: -------------------------------------------------------------------------------- 1 | import java.io.File 2 | 3 | package object common { 4 | 5 | /** An alias for the `Nothing` type. 6 | * Denotes that the type should be filled in. 7 | */ 8 | type ??? = Nothing 9 | 10 | /** An alias for the `Any` type. 11 | * Denotes that the type should be filled in. 12 | */ 13 | type *** = Any 14 | 15 | 16 | /** 17 | * Get a child of a file. For example, 18 | * 19 | * subFile(homeDir, "b", "c") 20 | * 21 | * corresponds to ~/b/c 22 | */ 23 | def subFile(file: File, children: String*) = { 24 | children.foldLeft(file)((file, child) => new File(file, child)) 25 | } 26 | 27 | /** 28 | * Get a resource from the `src/main/resources` directory. Eclipse does not copy 29 | * resources to the output directory, then the class loader cannot find them. 30 | */ 31 | def resourceAsStreamFromSrc(resourcePath: List[String]): Option[java.io.InputStream] = { 32 | val classesDir = new File(getClass.getResource(".").toURI) 33 | val projectDir = classesDir.getParentFile.getParentFile.getParentFile.getParentFile 34 | val resourceFile = subFile(projectDir, ("src" :: "main" :: "resources" :: resourcePath): _*) 35 | if (resourceFile.exists) 36 | Some(new java.io.FileInputStream(resourceFile)) 37 | else 38 | None 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /w3/nodescala/src/main/scala/nodescala/Main.scala: -------------------------------------------------------------------------------- 1 | package nodescala 2 | 3 | import scala.language.postfixOps 4 | import scala.concurrent._ 5 | import scala.concurrent.duration._ 6 | import ExecutionContext.Implicits.global 7 | import scala.async.Async.{async, await} 8 | 9 | object Main { 10 | 11 | def main(args: Array[String]) { 12 | // 1. instantiate the server at 8191, relative path "/test", 13 | // and have the response return headers of the request 14 | val myServer = new NodeScala.Default(8191) 15 | val myServerSubscription = myServer.start("/test") { request => 16 | for (kv <- request.iterator) yield (kv + "\n").toString 17 | } 18 | 19 | // 2. create a future that expects some user input `x` 20 | // and continues with a `"You entered... " + x` message 21 | val userInterrupted = Future.userInput("Hit ENTER to cancel... ") continueWith { 22 | f => "You entered... " + f.now 23 | } 24 | 25 | // TO IMPLEMENT 26 | // 3. create a future that completes after 20 seconds 27 | // and continues with a `"Server timeout!"` message 28 | val timeOut: Future[String] = ??? 29 | 30 | // TO IMPLEMENT 31 | // 4. create a future that completes when either 20 seconds elapse 32 | // or the user enters some text and presses ENTER 33 | val terminationRequested: Future[String] = ??? 34 | 35 | // TO IMPLEMENT 36 | // 5. unsubscribe from the server 37 | terminationRequested onSuccess { 38 | case msg => ??? 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /w3/nodescala/src/test/scala/nodescala/tests.scala: -------------------------------------------------------------------------------- 1 | package nodescala 2 | 3 | import scala.language.postfixOps 4 | import scala.util.{Try, Success, Failure} 5 | import scala.collection._ 6 | import scala.concurrent._ 7 | import ExecutionContext.Implicits.global 8 | import scala.concurrent.duration._ 9 | import scala.async.Async.{async, await} 10 | import org.scalatest._ 11 | import NodeScala._ 12 | import org.junit.runner.RunWith 13 | import org.scalatest.junit.JUnitRunner 14 | 15 | @RunWith(classOf[JUnitRunner]) 16 | class NodeScalaSuite extends FunSuite { 17 | 18 | test("A Future should always be completed") { 19 | val always = Future.always(517) 20 | 21 | assert(Await.result(always, 0 nanos) == 517) 22 | } 23 | test("A Future should never be completed") { 24 | val never = Future.never[Int] 25 | 26 | try { 27 | Await.result(never, 1 second) 28 | assert(false) 29 | } catch { 30 | case t: TimeoutException => // ok! 31 | } 32 | } 33 | 34 | 35 | 36 | class DummyExchange(val request: Request) extends Exchange { 37 | @volatile var response = "" 38 | val loaded = Promise[String]() 39 | def write(s: String) { 40 | response += s 41 | } 42 | def close() { 43 | loaded.success(response) 44 | } 45 | } 46 | 47 | class DummyListener(val port: Int, val relativePath: String) extends NodeScala.Listener { 48 | self => 49 | 50 | @volatile private var started = false 51 | var handler: Exchange => Unit = null 52 | 53 | def createContext(h: Exchange => Unit) = this.synchronized { 54 | assert(started, "is server started?") 55 | handler = h 56 | } 57 | 58 | def removeContext() = this.synchronized { 59 | assert(started, "is server started?") 60 | handler = null 61 | } 62 | 63 | def start() = self.synchronized { 64 | started = true 65 | new Subscription { 66 | def unsubscribe() = self.synchronized { 67 | started = false 68 | } 69 | } 70 | } 71 | 72 | def emit(req: Request) = { 73 | val exchange = new DummyExchange(req) 74 | if (handler != null) handler(exchange) 75 | exchange 76 | } 77 | } 78 | 79 | class DummyServer(val port: Int) extends NodeScala { 80 | self => 81 | val listeners = mutable.Map[String, DummyListener]() 82 | 83 | def createListener(relativePath: String) = { 84 | val l = new DummyListener(port, relativePath) 85 | listeners(relativePath) = l 86 | l 87 | } 88 | 89 | def emit(relativePath: String, req: Request) = this.synchronized { 90 | val l = listeners(relativePath) 91 | l.emit(req) 92 | } 93 | } 94 | test("Server should serve requests") { 95 | val dummy = new DummyServer(8191) 96 | val dummySubscription = dummy.start("/testDir") { 97 | request => for (kv <- request.iterator) yield (kv + "\n").toString 98 | } 99 | 100 | // wait until server is really installed 101 | Thread.sleep(500) 102 | 103 | def test(req: Request) { 104 | val webpage = dummy.emit("/testDir", req) 105 | val content = Await.result(webpage.loaded.future, 1 second) 106 | val expected = (for (kv <- req.iterator) yield (kv + "\n").toString).mkString 107 | assert(content == expected, s"'$content' vs. '$expected'") 108 | } 109 | 110 | test(immutable.Map("StrangeRequest" -> List("Does it work?"))) 111 | test(immutable.Map("StrangeRequest" -> List("It works!"))) 112 | test(immutable.Map("WorksForThree" -> List("Always works. Trust me."))) 113 | 114 | dummySubscription.unsubscribe() 115 | } 116 | 117 | } 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /w3/week3.nodescala-instructions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasnake/Principles-of-Reactive-Programming/66b432473a81342569394c331fc092b8f39505de/w3/week3.nodescala-instructions.pdf -------------------------------------------------------------------------------- /w4/suggestions/build.sbt: -------------------------------------------------------------------------------- 1 | submitProjectName := "suggestions" 2 | 3 | scalaVersion := "2.11.5" 4 | 5 | scalacOptions ++= Seq("-deprecation", "-feature") 6 | 7 | (fork in Test) := false 8 | 9 | projectDetailsMap := { 10 | val currentCourseId = "reactive-002" 11 | 12 | val depsNode = Seq( 13 | "io.reactivex" %% "rxscala" % "0.23.0", 14 | "io.reactivex" % "rxswing" % "0.21.0", // for Swing Scheduler in suggestions 15 | "org.json4s" %% "json4s-native" % "3.2.11", 16 | "org.scala-lang.modules" %% "scala-swing" % "1.0.1", 17 | "net.databinder.dispatch" %% "dispatch-core" % "0.11.0", 18 | "org.scala-lang" % "scala-reflect" % scalaVersion.value, 19 | "org.slf4j" % "slf4j-api" % "1.7.5", 20 | "org.slf4j" % "slf4j-simple" % "1.7.5", 21 | "com.squareup.retrofit" % "retrofit" % "1.9.0", 22 | "org.scala-lang.modules" %% "scala-async" % "0.9.2" 23 | ) 24 | 25 | val depsAkka = Seq( 26 | "com.typesafe.akka" %% "akka-actor" % "2.3.9", 27 | "com.typesafe.akka" %% "akka-testkit" % "2.3.9", 28 | "com.typesafe.akka" %% "akka-persistence-experimental" % "2.3.9" 29 | ) 30 | 31 | Map( 32 | "example" -> ProjectDetails( 33 | packageName = "example", 34 | assignmentPartId = "fTzFogNl", 35 | maxScore = 10d, 36 | styleScoreRatio = 0.0, 37 | courseId=currentCourseId), 38 | "quickcheck" -> ProjectDetails( 39 | packageName = "quickcheck", 40 | assignmentPartId = "02Vi5q7m", 41 | maxScore = 10d, 42 | styleScoreRatio = 0.0, 43 | courseId=currentCourseId, 44 | dependencies = Seq("org.scalacheck" %% "scalacheck" % "1.12.1")), 45 | "calculator" -> ProjectDetails( 46 | packageName = "calculator", 47 | assignmentPartId = "8uURtbi7", 48 | maxScore = 10d, 49 | styleScoreRatio = 0.0, 50 | courseId=currentCourseId), 51 | "nodescala" -> ProjectDetails( 52 | packageName = "nodescala", 53 | assignmentPartId = "RvoTAbRy", 54 | maxScore = 10d, 55 | styleScoreRatio = 0.0, 56 | courseId=currentCourseId, 57 | dependencies = depsNode), 58 | "suggestions" -> ProjectDetails( 59 | packageName = "suggestions", 60 | assignmentPartId = "rLLdQLGN", 61 | maxScore = 10d, 62 | styleScoreRatio = 0.0, 63 | courseId=currentCourseId), 64 | "actorbintree" -> ProjectDetails( 65 | packageName = "actorbintree", 66 | assignmentPartId = "VxIlIKoW", 67 | maxScore = 10d, 68 | styleScoreRatio = 0.0, 69 | courseId=currentCourseId, 70 | dependencies = depsAkka), 71 | "kvstore" -> ProjectDetails( 72 | packageName = "kvstore", 73 | assignmentPartId = "nuvh59Zi", 74 | maxScore = 20d, 75 | styleScoreRatio = 0.0, 76 | courseId=currentCourseId, 77 | dependencies = depsAkka) 78 | )} 79 | -------------------------------------------------------------------------------- /w4/suggestions/project/ReactiveBuild.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | import ch.epfl.lamp.CourseraBuild 4 | import ch.epfl.lamp.SbtCourseraPlugin.autoImport._ 5 | 6 | import org.scalajs.sbtplugin.ScalaJSPlugin 7 | import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ 8 | 9 | object ProgfunBuild extends CourseraBuild { 10 | override def assignmentSettings: Seq[Setting[_]] = Seq( 11 | // This setting allows to restrict the source files that are compiled and tested 12 | // to one specific project. It should be either the empty string, in which case all 13 | // projects are included, or one of the project names from the projectDetailsMap. 14 | currentProject := "", 15 | 16 | // Packages in src/main/scala that are used in every project. Included in every 17 | // handout, submission. 18 | commonSourcePackages += "common", 19 | 20 | // Packages in src/test/scala that are used for grading projects. Always included 21 | // compiling tests, grading a project. 22 | 23 | libraryDependencies += "ch.epfl.lamp" %% "scala-grading-runtime" % "0.1", 24 | 25 | // Files that we hand out to the students 26 | handoutFiles <<= (baseDirectory, projectDetailsMap, commonSourcePackages) map { 27 | (basedir, detailsMap, commonSrcs) => 28 | (projectName: String) => { 29 | val details = detailsMap.getOrElse(projectName, sys.error("Unknown project name: "+ projectName)) 30 | val commonFiles = (PathFinder.empty /: commonSrcs)((files, pkg) => 31 | files +++ (basedir / "src" / "main" / "scala" / pkg ** "*.scala") 32 | ) 33 | val forAll = { 34 | (basedir / "src" / "main" / "scala" / details.packageName ** "*.scala") +++ 35 | commonFiles +++ 36 | (basedir / "src" / "main" / "resources" / details.packageName / "*") +++ 37 | (basedir / "src" / "test" / "scala" / details.packageName ** "*.scala") +++ 38 | (basedir / "build.sbt") +++ 39 | (basedir / "project" / "build.properties") +++ 40 | (basedir / "project" ** ("*.scala" || "*.sbt")) +++ 41 | (basedir / "project" / "scalastyle_config_reactive.xml") +++ 42 | (basedir / "lib_managed" ** "*.jar") +++ 43 | (basedir * (".classpath" || ".project")) +++ 44 | (basedir / ".settings" / "org.scala-ide.sdt.core.prefs") 45 | } 46 | if (projectName == "calculator") { 47 | forAll +++ 48 | (basedir / "webui.sbt") +++ 49 | (basedir / "web-ui" / "index.html") +++ 50 | (basedir / "web-ui" / "src" / "main" / "scala" ** "*.scala") 51 | } else 52 | forAll 53 | } 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /w4/suggestions/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /w4/suggestions/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("ch.epfl.lamp" % "sbt-coursera" % "0.5") 2 | 3 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.2") 4 | -------------------------------------------------------------------------------- /w4/suggestions/src/main/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasnake/Principles-of-Reactive-Programming/66b432473a81342569394c331fc092b8f39505de/w4/suggestions/src/main/.DS_Store -------------------------------------------------------------------------------- /w4/suggestions/src/main/resources/suggestions/wiki-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasnake/Principles-of-Reactive-Programming/66b432473a81342569394c331fc092b8f39505de/w4/suggestions/src/main/resources/suggestions/wiki-icon.png -------------------------------------------------------------------------------- /w4/suggestions/src/main/scala/common/package.scala: -------------------------------------------------------------------------------- 1 | import java.io.File 2 | 3 | package object common { 4 | 5 | /** An alias for the `Nothing` type. 6 | * Denotes that the type should be filled in. 7 | */ 8 | type ??? = Nothing 9 | 10 | /** An alias for the `Any` type. 11 | * Denotes that the type should be filled in. 12 | */ 13 | type *** = Any 14 | 15 | 16 | /** 17 | * Get a child of a file. For example, 18 | * 19 | * subFile(homeDir, "b", "c") 20 | * 21 | * corresponds to ~/b/c 22 | */ 23 | def subFile(file: File, children: String*) = { 24 | children.foldLeft(file)((file, child) => new File(file, child)) 25 | } 26 | 27 | /** 28 | * Get a resource from the `src/main/resources` directory. Eclipse does not copy 29 | * resources to the output directory, then the class loader cannot find them. 30 | */ 31 | def resourceAsStreamFromSrc(resourcePath: List[String]): Option[java.io.InputStream] = { 32 | val classesDir = new File(getClass.getResource(".").toURI) 33 | val projectDir = classesDir.getParentFile.getParentFile.getParentFile.getParentFile 34 | val resourceFile = subFile(projectDir, ("src" :: "main" :: "resources" :: resourcePath): _*) 35 | if (resourceFile.exists) 36 | Some(new java.io.FileInputStream(resourceFile)) 37 | else 38 | None 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /w4/suggestions/src/main/scala/suggestions/gui/SwingApi.scala: -------------------------------------------------------------------------------- 1 | package suggestions 2 | package gui 3 | 4 | import scala.language.reflectiveCalls 5 | import scala.collection.mutable.ListBuffer 6 | import scala.collection.JavaConverters._ 7 | import scala.concurrent._ 8 | import scala.concurrent.ExecutionContext.Implicits.global 9 | import scala.util.{ Try, Success, Failure } 10 | import scala.swing.Reactions.Reaction 11 | import scala.swing.event.Event 12 | import rx.lang.scala.Observable 13 | 14 | /** Basic facilities for dealing with Swing-like components. 15 | * 16 | * Instead of committing to a particular widget implementation 17 | * functionality has been factored out here to deal only with 18 | * abstract types like `ValueChanged` or `TextField`. 19 | * Extractors for abstract events like `ValueChanged` have also 20 | * been factored out into corresponding abstract `val`s. 21 | */ 22 | trait SwingApi { 23 | 24 | type ValueChanged <: Event 25 | 26 | val ValueChanged: { 27 | def unapply(x: Event): Option[TextField] 28 | } 29 | 30 | type ButtonClicked <: Event 31 | 32 | val ButtonClicked: { 33 | def unapply(x: Event): Option[Button] 34 | } 35 | 36 | type TextField <: { 37 | def text: String 38 | def subscribe(r: Reaction): Unit 39 | def unsubscribe(r: Reaction): Unit 40 | } 41 | 42 | type Button <: { 43 | def subscribe(r: Reaction): Unit 44 | def unsubscribe(r: Reaction): Unit 45 | } 46 | 47 | implicit class TextFieldOps(field: TextField) { 48 | 49 | /** Returns a stream of text field values entered in the given text field. 50 | * 51 | * @param field the text field 52 | * @return an observable with a stream of text field updates 53 | */ 54 | def textValues: Observable[String] = ??? 55 | 56 | } 57 | 58 | implicit class ButtonOps(button: Button) { 59 | 60 | /** Returns a stream of button clicks. 61 | * 62 | * @param field the button 63 | * @return an observable with a stream of buttons that have been clicked 64 | */ 65 | def clicks: Observable[Button] = ??? 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /w4/suggestions/src/main/scala/suggestions/gui/WikipediaApi.scala: -------------------------------------------------------------------------------- 1 | package suggestions 2 | package gui 3 | 4 | import scala.language.postfixOps 5 | import scala.collection.mutable.ListBuffer 6 | import scala.collection.JavaConverters._ 7 | import scala.concurrent._ 8 | import scala.concurrent.duration._ 9 | import scala.concurrent.ExecutionContext.Implicits.global 10 | import scala.util.{ Try, Success, Failure } 11 | import rx.subscriptions.CompositeSubscription 12 | import rx.lang.scala.Observable 13 | import observablex._ 14 | import search._ 15 | 16 | trait WikipediaApi { 17 | 18 | /** Returns a `Future` with a list of possible completions for a search `term`. 19 | */ 20 | def wikipediaSuggestion(term: String): Future[List[String]] 21 | 22 | /** Returns a `Future` with the contents of the Wikipedia page for the given search `term`. 23 | */ 24 | def wikipediaPage(term: String): Future[String] 25 | 26 | /** Returns an `Observable` with a list of possible completions for a search `term`. 27 | */ 28 | def wikiSuggestResponseStream(term: String): Observable[List[String]] = ObservableEx(wikipediaSuggestion(term)).timedOut(1L) 29 | 30 | /** Returns an `Observable` with the contents of the Wikipedia page for the given search `term`. 31 | */ 32 | def wikiPageResponseStream(term: String): Observable[String] = ObservableEx(wikipediaPage(term)).timedOut(1L) 33 | 34 | implicit class StringObservableOps(obs: Observable[String]) { 35 | 36 | /** Given a stream of search terms, returns a stream of search terms with spaces replaced by underscores. 37 | * 38 | * E.g. `"erik", "erik meijer", "martin` should become `"erik", "erik_meijer", "martin"` 39 | */ 40 | def sanitized: Observable[String] = ??? 41 | 42 | } 43 | 44 | implicit class ObservableOps[T](obs: Observable[T]) { 45 | 46 | /** Given an observable that can possibly be completed with an error, returns a new observable 47 | * with the same values wrapped into `Success` and the potential error wrapped into `Failure`. 48 | * 49 | * E.g. `1, 2, 3, !Exception!` should become `Success(1), Success(2), Success(3), Failure(Exception), !TerminateStream!` 50 | */ 51 | def recovered: Observable[Try[T]] = ??? 52 | 53 | /** Emits the events from the `obs` observable, until `totalSec` seconds have elapsed. 54 | * 55 | * After `totalSec` seconds, if `obs` is not yet completed, the result observable becomes completed. 56 | * 57 | * Note: uses the existing combinators on observables. 58 | */ 59 | def timedOut(totalSec: Long): Observable[T] = ??? 60 | 61 | /** Given a stream of events `obs` and a method `requestMethod` to map a request `T` into 62 | * a stream of responses `S`, returns a stream of all the responses wrapped into a `Try`. 63 | * The elements of the response stream should reflect the order of their corresponding events in `obs`. 64 | * 65 | * E.g. given a request stream: 66 | * 67 | * 1, 2, 3, 4, 5 68 | * 69 | * And a request method: 70 | * 71 | * num => if (num != 4) Observable.just(num) else Observable.error(new Exception) 72 | * 73 | * We should, for example, get: 74 | * 75 | * Success(1), Success(2), Success(3), Failure(new Exception), Success(5) 76 | * 77 | * 78 | * Similarly: 79 | * 80 | * Observable(1, 2, 3).concatRecovered(num => Observable(num, num, num)) 81 | * 82 | * should return: 83 | * 84 | * Observable(Success(1), Succeess(1), Succeess(1), Succeess(2), Succeess(2), Succeess(2), Succeess(3), Succeess(3), Succeess(3)) 85 | */ 86 | def concatRecovered[S](requestMethod: T => Observable[S]): Observable[Try[S]] = ??? 87 | 88 | } 89 | 90 | } 91 | 92 | -------------------------------------------------------------------------------- /w4/suggestions/src/main/scala/suggestions/gui/WikipediaSuggest.scala: -------------------------------------------------------------------------------- 1 | package suggestions 2 | package gui 3 | 4 | import scala.collection.mutable.ListBuffer 5 | import scala.collection.JavaConverters._ 6 | import scala.concurrent._ 7 | import scala.concurrent.ExecutionContext.Implicits.global 8 | import scala.swing._ 9 | import scala.util.{ Try, Success, Failure } 10 | import scala.swing.event._ 11 | import swing.Swing._ 12 | import javax.swing.UIManager 13 | import Orientation._ 14 | import rx.subscriptions.CompositeSubscription 15 | import rx.lang.scala.Observable 16 | import rx.lang.scala.Subscription 17 | import observablex._ 18 | import search._ 19 | 20 | object WikipediaSuggest extends SimpleSwingApplication with ConcreteSwingApi with ConcreteWikipediaApi { 21 | 22 | { 23 | try { 24 | UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()) 25 | } catch { 26 | case t: Throwable => 27 | } 28 | } 29 | 30 | def top = new MainFrame { 31 | 32 | /* gui setup */ 33 | 34 | title = "Query Wikipedia" 35 | minimumSize = new Dimension(900, 600) 36 | 37 | val button = new Button("Get") { 38 | icon = new javax.swing.ImageIcon(javax.imageio.ImageIO.read(this.getClass.getResourceAsStream("/suggestions/wiki-icon.png"))) 39 | } 40 | val searchTermField = new TextField 41 | val suggestionList = new ListView(ListBuffer[String]()) 42 | val status = new Label(" ") 43 | val editorpane = new EditorPane { 44 | import javax.swing.border._ 45 | border = new EtchedBorder(EtchedBorder.LOWERED) 46 | editable = false 47 | peer.setContentType("text/html") 48 | } 49 | 50 | contents = new BoxPanel(orientation = Vertical) { 51 | border = EmptyBorder(top = 5, left = 5, bottom = 5, right = 5) 52 | contents += new BoxPanel(orientation = Horizontal) { 53 | contents += new BoxPanel(orientation = Vertical) { 54 | maximumSize = new Dimension(240, 900) 55 | border = EmptyBorder(top = 10, left = 10, bottom = 10, right = 10) 56 | contents += new BoxPanel(orientation = Horizontal) { 57 | maximumSize = new Dimension(640, 30) 58 | border = EmptyBorder(top = 5, left = 0, bottom = 5, right = 0) 59 | contents += searchTermField 60 | } 61 | contents += new ScrollPane(suggestionList) 62 | contents += new BorderPanel { 63 | maximumSize = new Dimension(640, 30) 64 | add(button, BorderPanel.Position.Center) 65 | } 66 | } 67 | contents += new ScrollPane(editorpane) 68 | } 69 | contents += status 70 | } 71 | 72 | val eventScheduler = SchedulerEx.SwingEventThreadScheduler 73 | 74 | /** 75 | * Observables 76 | * You may find the following methods useful when manipulating GUI elements: 77 | * `myListView.listData = aList` : sets the content of `myListView` to `aList` 78 | * `myTextField.text = "react"` : sets the content of `myTextField` to "react" 79 | * `myListView.selection.items` returns a list of selected items from `myListView` 80 | * `myEditorPane.text = "act"` : sets the content of `myEditorPane` to "act" 81 | */ 82 | 83 | // TO IMPLEMENT 84 | val searchTerms: Observable[String] = ??? 85 | 86 | // TO IMPLEMENT 87 | val suggestions: Observable[Try[List[String]]] = ??? 88 | 89 | // TO IMPLEMENT 90 | val suggestionSubscription: Subscription = suggestions.observeOn(eventScheduler) subscribe { 91 | x => ??? 92 | } 93 | 94 | // TO IMPLEMENT 95 | val selections: Observable[String] = ??? 96 | 97 | // TO IMPLEMENT 98 | val pages: Observable[Try[String]] = ??? 99 | 100 | // TO IMPLEMENT 101 | val pageSubscription: Subscription = pages.observeOn(eventScheduler) subscribe { 102 | x => ??? 103 | } 104 | 105 | } 106 | 107 | } 108 | 109 | 110 | trait ConcreteWikipediaApi extends WikipediaApi { 111 | def wikipediaSuggestion(term: String) = Search.wikipediaSuggestion(term) 112 | def wikipediaPage(term: String) = Search.wikipediaPage(term) 113 | } 114 | 115 | 116 | trait ConcreteSwingApi extends SwingApi { 117 | type ValueChanged = scala.swing.event.ValueChanged 118 | object ValueChanged { 119 | def unapply(x: Event) = x match { 120 | case vc: ValueChanged => Some(vc.source.asInstanceOf[TextField]) 121 | case _ => None 122 | } 123 | } 124 | type ButtonClicked = scala.swing.event.ButtonClicked 125 | object ButtonClicked { 126 | def unapply(x: Event) = x match { 127 | case bc: ButtonClicked => Some(bc.source.asInstanceOf[Button]) 128 | case _ => None 129 | } 130 | } 131 | type TextField = scala.swing.TextField 132 | type Button = scala.swing.Button 133 | } 134 | -------------------------------------------------------------------------------- /w4/suggestions/src/main/scala/suggestions/gui/package.scala: -------------------------------------------------------------------------------- 1 | package suggestions 2 | 3 | import scala.swing.Reactions.Reaction 4 | 5 | package object gui { 6 | 7 | object Reaction { 8 | def apply(r: Reaction) = r 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /w4/suggestions/src/main/scala/suggestions/observablex/ObservableEx.scala: -------------------------------------------------------------------------------- 1 | package suggestions 2 | package observablex 3 | 4 | import scala.concurrent.{Future, ExecutionContext} 5 | import scala.util._ 6 | import scala.util.Success 7 | import scala.util.Failure 8 | import java.lang.Throwable 9 | import rx.lang.scala.Observable 10 | import rx.lang.scala.Scheduler 11 | 12 | object ObservableEx { 13 | 14 | /** Returns an observable stream of values produced by the given future. 15 | * If the future fails, the observable will fail as well. 16 | * 17 | * @param f future whose values end up in the resulting observable 18 | * @return an observable completed after producing the value of the future, or with an exception 19 | */ 20 | def apply[T](f: Future[T])(implicit execContext: ExecutionContext): Observable[T] = { 21 | Observable.from(f) 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /w4/suggestions/src/main/scala/suggestions/observablex/SchedulerEx.scala: -------------------------------------------------------------------------------- 1 | package suggestions 2 | package observablex 3 | 4 | import rx.lang.scala.Scheduler 5 | import rx.schedulers.SwingScheduler 6 | 7 | object SchedulerEx { 8 | 9 | val SwingEventThreadScheduler: Scheduler = 10 | rx.lang.scala.JavaConversions.javaSchedulerToScalaScheduler(SwingScheduler.getInstance) 11 | 12 | } 13 | -------------------------------------------------------------------------------- /w4/suggestions/src/main/scala/suggestions/package.scala: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | package object suggestions { 6 | 7 | def log(x: Any) = println(x) 8 | 9 | } 10 | -------------------------------------------------------------------------------- /w4/suggestions/src/main/scala/suggestions/search/Search.scala: -------------------------------------------------------------------------------- 1 | package suggestions 2 | package search 3 | 4 | import org.json4s._ 5 | import scala.concurrent.{ ExecutionContext, Future, Promise } 6 | import ExecutionContext.Implicits.global 7 | import scala.language.postfixOps 8 | import scala.collection._ 9 | import scala.collection.JavaConverters._ 10 | import scala.util.Try 11 | import scala.async.Async._ 12 | 13 | import rx.lang.scala.Observable 14 | import observablex.{SchedulerEx, ObservableEx} 15 | import ObservableEx._ 16 | 17 | import dispatch._ 18 | import org.json4s.native._ 19 | import retrofit.http.{GET, Query} 20 | import retrofit.Callback 21 | import retrofit.client.Response 22 | import retrofit.{RetrofitError, Callback, RestAdapter} 23 | import com.google.gson.annotations.SerializedName 24 | 25 | object Search { 26 | 27 | /* implementation using Json */ 28 | 29 | implicit val formats = org.json4s.DefaultFormats 30 | 31 | def wikipediaSuggestionJson(term: String): Future[List[String]] = { 32 | async { 33 | log("querying: " + term) 34 | val search = "http://en.wikipedia.org/w/api.php?action=opensearch&format=json&limit=15&search=" 35 | val response = await { Http(url(search + term).OK(as.String)) } 36 | val json = JsonParser.parse(response) 37 | val words = json(1) 38 | words.extract[List[String]] 39 | } 40 | } 41 | 42 | def wikipediaPageJson(term: String): Future[String] = { 43 | async { 44 | val search = "http://en.wikipedia.org/w/api.php?action=parse&format=json&prop=text§ion=0&page=" 45 | val response = await { Http(url(search + term).OK(as.String)) } 46 | val json = JsonParser.parse(response) 47 | val text = for { 48 | JObject(child) <- json 49 | JField("parse", JObject(fields)) <- child 50 | JField("text", JObject(tfields)) <- fields 51 | JField("*", JString(text)) <- tfields 52 | } yield text 53 | text.head 54 | } 55 | } 56 | 57 | /* alternative implementation using Retrofit */ 58 | 59 | class Page { 60 | var parse: Content = _ 61 | } 62 | 63 | class Content { 64 | var title: String = _ 65 | var text: Text = _ 66 | } 67 | 68 | class Text { 69 | @SerializedName("*") 70 | var all: String = _ 71 | } 72 | 73 | /** 74 | * retrofit need to be updated to 1.9 for https support, 75 | * old API url is not working anymore, 76 | * 77 | * new url: 78 | * https://en.wikipedia.org/w/api.php?action=opensearch&limit=15&format=json&search=scala 79 | */ 80 | trait WikipediaService { 81 | @GET("/w/api.php?action=opensearch&format=json&limit=15") 82 | def suggestions(@Query("search") term: String, callback: Callback[Array[AnyRef]]): Unit 83 | 84 | @GET("/w/api.php?action=parse&format=json&prop=text§ion=0") 85 | def page(@Query("page") term: String, callback: Callback[Page]): Unit 86 | } 87 | 88 | val restAdapter = new RestAdapter.Builder().setEndpoint("https://en.wikipedia.org").build() 89 | 90 | val service = restAdapter.create(classOf[WikipediaService]) 91 | 92 | def callbackFuture[T]: (Callback[T], Future[T]) = { 93 | val p = Promise[T]() 94 | val cb = new Callback[T] { 95 | def success(t: T, response: Response) = { 96 | p success t 97 | } 98 | def failure(error: RetrofitError) = { 99 | p failure error 100 | } 101 | } 102 | 103 | (cb, p.future) 104 | } 105 | 106 | def wikipediaSuggestionRetrofit(term: String): Future[List[String]] = { 107 | async { 108 | val (cb, f) = callbackFuture[Array[AnyRef]] 109 | service.suggestions(term, cb) 110 | val result = await { f } 111 | val arraylist = result(1).asInstanceOf[java.util.List[String]] 112 | 113 | arraylist.asScala.toList 114 | } 115 | } 116 | 117 | def wikipediaPageRetrofit(term: String): Future[String] = { 118 | async { 119 | val (cb, f) = callbackFuture[Page] 120 | service.page(term, cb) 121 | val result = await { f } 122 | result.parse.text.all 123 | } 124 | } 125 | 126 | def wikipediaSuggestion(term: String): Future[List[String]] = wikipediaSuggestionRetrofit(term) 127 | 128 | def wikipediaPage(term: String): Future[String] = wikipediaPageRetrofit(term) 129 | 130 | } 131 | 132 | -------------------------------------------------------------------------------- /w4/suggestions/src/test/scala/suggestions/SwingApiTest.scala: -------------------------------------------------------------------------------- 1 | package suggestions 2 | 3 | 4 | 5 | import scala.collection._ 6 | import scala.concurrent._ 7 | import scala.concurrent.ExecutionContext.Implicits.global 8 | import scala.util.{Try, Success, Failure} 9 | import scala.swing.event.Event 10 | import scala.swing.Reactions.Reaction 11 | import rx.lang.scala._ 12 | import org.scalatest._ 13 | import gui._ 14 | 15 | import org.junit.runner.RunWith 16 | import org.scalatest.junit.JUnitRunner 17 | 18 | @RunWith(classOf[JUnitRunner]) 19 | class SwingApiTest extends FunSuite { 20 | 21 | object swingApi extends SwingApi { 22 | class ValueChanged(val textField: TextField) extends Event 23 | 24 | object ValueChanged { 25 | def unapply(x: Event) = x match { 26 | case vc: ValueChanged => Some(vc.textField) 27 | case _ => None 28 | } 29 | } 30 | 31 | class ButtonClicked(val source: Button) extends Event 32 | 33 | object ButtonClicked { 34 | def unapply(x: Event) = x match { 35 | case bc: ButtonClicked => Some(bc.source) 36 | case _ => None 37 | } 38 | } 39 | 40 | class Component { 41 | private val subscriptions = mutable.Set[Reaction]() 42 | def subscribe(r: Reaction) { 43 | subscriptions add r 44 | } 45 | def unsubscribe(r: Reaction) { 46 | subscriptions remove r 47 | } 48 | def publish(e: Event) { 49 | for (r <- subscriptions) r(e) 50 | } 51 | } 52 | 53 | class TextField extends Component { 54 | private var _text = "" 55 | def text = _text 56 | def text_=(t: String) { 57 | _text = t 58 | publish(new ValueChanged(this)) 59 | } 60 | } 61 | 62 | class Button extends Component { 63 | def click() { 64 | publish(new ButtonClicked(this)) 65 | } 66 | } 67 | } 68 | 69 | import swingApi._ 70 | 71 | test("SwingApi should emit text field values to the observable") { 72 | val textField = new swingApi.TextField 73 | val values = textField.textValues 74 | 75 | val observed = mutable.Buffer[String]() 76 | val sub = values subscribe { 77 | observed += _ 78 | } 79 | 80 | // write some text now 81 | textField.text = "T" 82 | textField.text = "Tu" 83 | textField.text = "Tur" 84 | textField.text = "Turi" 85 | textField.text = "Turin" 86 | textField.text = "Turing" 87 | 88 | assert(observed == Seq("T", "Tu", "Tur", "Turi", "Turin", "Turing"), observed) 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /w4/suggestions/src/test/scala/suggestions/WikipediaApiTest.scala: -------------------------------------------------------------------------------- 1 | package suggestions 2 | 3 | 4 | 5 | import language.postfixOps 6 | import scala.concurrent._ 7 | import scala.concurrent.duration._ 8 | import scala.concurrent.ExecutionContext.Implicits.global 9 | import scala.util.{Try, Success, Failure} 10 | import rx.lang.scala._ 11 | import org.scalatest._ 12 | import gui._ 13 | 14 | import org.junit.runner.RunWith 15 | import org.scalatest.junit.JUnitRunner 16 | 17 | 18 | @RunWith(classOf[JUnitRunner]) 19 | class WikipediaApiTest extends FunSuite { 20 | 21 | object mockApi extends WikipediaApi { 22 | def wikipediaSuggestion(term: String) = Future { 23 | if (term.head.isLetter) { 24 | for (suffix <- List(" (Computer Scientist)", " (Footballer)")) yield term + suffix 25 | } else { 26 | List(term) 27 | } 28 | } 29 | def wikipediaPage(term: String) = Future { 30 | "Title: " + term 31 | } 32 | } 33 | 34 | import mockApi._ 35 | 36 | test("WikipediaApi should make the stream valid using sanitized") { 37 | val notvalid = Observable.just("erik", "erik meijer", "martin") 38 | val valid = notvalid.sanitized 39 | 40 | var count = 0 41 | var completed = false 42 | 43 | val sub = valid.subscribe( 44 | term => { 45 | assert(term.forall(_ != ' ')) 46 | count += 1 47 | }, 48 | t => assert(false, s"stream error $t"), 49 | () => completed = true 50 | ) 51 | assert(completed && count == 3, "completed: " + completed + ", event count: " + count) 52 | } 53 | test("WikipediaApi should correctly use concatRecovered") { 54 | val requests = Observable.just(1, 2, 3) 55 | val remoteComputation = (n: Int) => Observable.just(0 to n : _*) 56 | val responses = requests concatRecovered remoteComputation 57 | val sum = responses.foldLeft(0) { (acc, tn) => 58 | tn match { 59 | case Success(n) => acc + n 60 | case Failure(t) => throw t 61 | } 62 | } 63 | var total = -1 64 | val sub = sum.subscribe { 65 | s => total = s 66 | } 67 | assert(total == (1 + 1 + 2 + 1 + 2 + 3), s"Sum: $total") 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /w4/week4.suggestions-instructions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasnake/Principles-of-Reactive-Programming/66b432473a81342569394c331fc092b8f39505de/w4/week4.suggestions-instructions.pdf -------------------------------------------------------------------------------- /w5/actorbintree/build.sbt: -------------------------------------------------------------------------------- 1 | submitProjectName := "actorbintree" 2 | 3 | scalaVersion := "2.11.5" 4 | 5 | scalacOptions ++= Seq("-deprecation", "-feature") 6 | 7 | (fork in Test) := false 8 | 9 | projectDetailsMap := { 10 | val currentCourseId = "reactive-002" 11 | 12 | val depsNode = Seq( 13 | "io.reactivex" %% "rxscala" % "0.23.0", 14 | "io.reactivex" % "rxswing" % "0.21.0", // for Swing Scheduler in suggestions 15 | "org.json4s" %% "json4s-native" % "3.2.11", 16 | "org.scala-lang.modules" %% "scala-swing" % "1.0.1", 17 | "net.databinder.dispatch" %% "dispatch-core" % "0.11.0", 18 | "org.scala-lang" % "scala-reflect" % scalaVersion.value, 19 | "org.slf4j" % "slf4j-api" % "1.7.5", 20 | "org.slf4j" % "slf4j-simple" % "1.7.5", 21 | "com.squareup.retrofit" % "retrofit" % "1.0.0", 22 | "org.scala-lang.modules" %% "scala-async" % "0.9.2" 23 | ) 24 | 25 | val depsAkka = Seq( 26 | "com.typesafe.akka" %% "akka-actor" % "2.3.9", 27 | "com.typesafe.akka" %% "akka-testkit" % "2.3.9", 28 | "com.typesafe.akka" %% "akka-persistence-experimental" % "2.3.9" 29 | ) 30 | 31 | Map( 32 | "example" -> ProjectDetails( 33 | packageName = "example", 34 | assignmentPartId = "fTzFogNl", 35 | maxScore = 10d, 36 | styleScoreRatio = 0.0, 37 | courseId=currentCourseId), 38 | "quickcheck" -> ProjectDetails( 39 | packageName = "quickcheck", 40 | assignmentPartId = "02Vi5q7m", 41 | maxScore = 10d, 42 | styleScoreRatio = 0.0, 43 | courseId=currentCourseId, 44 | dependencies = Seq("org.scalacheck" %% "scalacheck" % "1.12.1")), 45 | "calculator" -> ProjectDetails( 46 | packageName = "calculator", 47 | assignmentPartId = "8uURtbi7", 48 | maxScore = 10d, 49 | styleScoreRatio = 0.0, 50 | courseId=currentCourseId), 51 | "nodescala" -> ProjectDetails( 52 | packageName = "nodescala", 53 | assignmentPartId = "RvoTAbRy", 54 | maxScore = 10d, 55 | styleScoreRatio = 0.0, 56 | courseId=currentCourseId, 57 | dependencies = depsNode), 58 | "suggestions" -> ProjectDetails( 59 | packageName = "suggestions", 60 | assignmentPartId = "rLLdQLGN", 61 | maxScore = 10d, 62 | styleScoreRatio = 0.0, 63 | courseId=currentCourseId), 64 | "actorbintree" -> ProjectDetails( 65 | packageName = "actorbintree", 66 | assignmentPartId = "VxIlIKoW", 67 | maxScore = 10d, 68 | styleScoreRatio = 0.0, 69 | courseId=currentCourseId, 70 | dependencies = depsAkka), 71 | "kvstore" -> ProjectDetails( 72 | packageName = "kvstore", 73 | assignmentPartId = "nuvh59Zi", 74 | maxScore = 20d, 75 | styleScoreRatio = 0.0, 76 | courseId=currentCourseId, 77 | dependencies = depsAkka) 78 | )} 79 | -------------------------------------------------------------------------------- /w5/actorbintree/project/ReactiveBuild.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | import ch.epfl.lamp.CourseraBuild 4 | import ch.epfl.lamp.SbtCourseraPlugin.autoImport._ 5 | 6 | import org.scalajs.sbtplugin.ScalaJSPlugin 7 | import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ 8 | 9 | object ProgfunBuild extends CourseraBuild { 10 | override def assignmentSettings: Seq[Setting[_]] = Seq( 11 | // This setting allows to restrict the source files that are compiled and tested 12 | // to one specific project. It should be either the empty string, in which case all 13 | // projects are included, or one of the project names from the projectDetailsMap. 14 | currentProject := "", 15 | 16 | // Packages in src/main/scala that are used in every project. Included in every 17 | // handout, submission. 18 | commonSourcePackages += "common", 19 | 20 | // Packages in src/test/scala that are used for grading projects. Always included 21 | // compiling tests, grading a project. 22 | 23 | libraryDependencies += "ch.epfl.lamp" %% "scala-grading-runtime" % "0.1", 24 | 25 | // Files that we hand out to the students 26 | handoutFiles <<= (baseDirectory, projectDetailsMap, commonSourcePackages) map { 27 | (basedir, detailsMap, commonSrcs) => 28 | (projectName: String) => { 29 | val details = detailsMap.getOrElse(projectName, sys.error("Unknown project name: "+ projectName)) 30 | val commonFiles = (PathFinder.empty /: commonSrcs)((files, pkg) => 31 | files +++ (basedir / "src" / "main" / "scala" / pkg ** "*.scala") 32 | ) 33 | val forAll = { 34 | (basedir / "src" / "main" / "scala" / details.packageName ** "*.scala") +++ 35 | commonFiles +++ 36 | (basedir / "src" / "main" / "resources" / details.packageName / "*") +++ 37 | (basedir / "src" / "test" / "scala" / details.packageName ** "*.scala") +++ 38 | (basedir / "build.sbt") +++ 39 | (basedir / "project" / "build.properties") +++ 40 | (basedir / "project" ** ("*.scala" || "*.sbt")) +++ 41 | (basedir / "project" / "scalastyle_config_reactive.xml") +++ 42 | (basedir / "lib_managed" ** "*.jar") +++ 43 | (basedir * (".classpath" || ".project")) +++ 44 | (basedir / ".settings" / "org.scala-ide.sdt.core.prefs") 45 | } 46 | if (projectName == "calculator") { 47 | forAll +++ 48 | (basedir / "webui.sbt") +++ 49 | (basedir / "web-ui" / "index.html") +++ 50 | (basedir / "web-ui" / "src" / "main" / "scala" ** "*.scala") 51 | } else 52 | forAll 53 | } 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /w5/actorbintree/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /w5/actorbintree/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("ch.epfl.lamp" % "sbt-coursera" % "0.5") 2 | 3 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.2") 4 | -------------------------------------------------------------------------------- /w5/actorbintree/src/main/scala/actorbintree/BinaryTreeSet.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2009-2013 Typesafe Inc. 3 | */ 4 | package actorbintree 5 | 6 | import akka.actor._ 7 | import scala.collection.immutable.Queue 8 | 9 | object BinaryTreeSet { 10 | 11 | trait Operation { 12 | def requester: ActorRef 13 | def id: Int 14 | def elem: Int 15 | } 16 | 17 | trait OperationReply { 18 | def id: Int 19 | } 20 | 21 | /** Request with identifier `id` to insert an element `elem` into the tree. 22 | * The actor at reference `requester` should be notified when this operation 23 | * is completed. 24 | */ 25 | case class Insert(requester: ActorRef, id: Int, elem: Int) extends Operation 26 | 27 | /** Request with identifier `id` to check whether an element `elem` is present 28 | * in the tree. The actor at reference `requester` should be notified when 29 | * this operation is completed. 30 | */ 31 | case class Contains(requester: ActorRef, id: Int, elem: Int) extends Operation 32 | 33 | /** Request with identifier `id` to remove the element `elem` from the tree. 34 | * The actor at reference `requester` should be notified when this operation 35 | * is completed. 36 | */ 37 | case class Remove(requester: ActorRef, id: Int, elem: Int) extends Operation 38 | 39 | /** Request to perform garbage collection*/ 40 | case object GC 41 | 42 | /** Holds the answer to the Contains request with identifier `id`. 43 | * `result` is true if and only if the element is present in the tree. 44 | */ 45 | case class ContainsResult(id: Int, result: Boolean) extends OperationReply 46 | 47 | /** Message to signal successful completion of an insert or remove operation. */ 48 | case class OperationFinished(id: Int) extends OperationReply 49 | 50 | } 51 | 52 | 53 | class BinaryTreeSet extends Actor { 54 | import BinaryTreeSet._ 55 | import BinaryTreeNode._ 56 | 57 | def createRoot: ActorRef = context.actorOf(BinaryTreeNode.props(0, initiallyRemoved = true)) 58 | 59 | var root = createRoot 60 | 61 | // optional 62 | var pendingQueue = Queue.empty[Operation] 63 | 64 | // optional 65 | def receive = normal 66 | 67 | // optional 68 | /** Accepts `Operation` and `GC` messages. */ 69 | val normal: Receive = { case _ => ??? } 70 | 71 | // optional 72 | /** Handles messages while garbage collection is performed. 73 | * `newRoot` is the root of the new binary tree where we want to copy 74 | * all non-removed elements into. 75 | */ 76 | def garbageCollecting(newRoot: ActorRef): Receive = ??? 77 | 78 | } 79 | 80 | object BinaryTreeNode { 81 | trait Position 82 | 83 | case object Left extends Position 84 | case object Right extends Position 85 | 86 | case class CopyTo(treeNode: ActorRef) 87 | case object CopyFinished 88 | 89 | def props(elem: Int, initiallyRemoved: Boolean) = Props(classOf[BinaryTreeNode], elem, initiallyRemoved) 90 | } 91 | 92 | class BinaryTreeNode(val elem: Int, initiallyRemoved: Boolean) extends Actor { 93 | import BinaryTreeNode._ 94 | import BinaryTreeSet._ 95 | 96 | var subtrees = Map[Position, ActorRef]() 97 | var removed = initiallyRemoved 98 | 99 | // optional 100 | def receive = normal 101 | 102 | // optional 103 | /** Handles `Operation` messages and `CopyTo` requests. */ 104 | val normal: Receive = { case _ => ??? } 105 | 106 | // optional 107 | /** `expected` is the set of ActorRefs whose replies we are waiting for, 108 | * `insertConfirmed` tracks whether the copy of this node to the new tree has been confirmed. 109 | */ 110 | def copying(expected: Set[ActorRef], insertConfirmed: Boolean): Receive = ??? 111 | 112 | 113 | } 114 | -------------------------------------------------------------------------------- /w5/actorbintree/src/main/scala/common/package.scala: -------------------------------------------------------------------------------- 1 | import java.io.File 2 | 3 | package object common { 4 | 5 | /** An alias for the `Nothing` type. 6 | * Denotes that the type should be filled in. 7 | */ 8 | type ??? = Nothing 9 | 10 | /** An alias for the `Any` type. 11 | * Denotes that the type should be filled in. 12 | */ 13 | type *** = Any 14 | 15 | 16 | /** 17 | * Get a child of a file. For example, 18 | * 19 | * subFile(homeDir, "b", "c") 20 | * 21 | * corresponds to ~/b/c 22 | */ 23 | def subFile(file: File, children: String*) = { 24 | children.foldLeft(file)((file, child) => new File(file, child)) 25 | } 26 | 27 | /** 28 | * Get a resource from the `src/main/resources` directory. Eclipse does not copy 29 | * resources to the output directory, then the class loader cannot find them. 30 | */ 31 | def resourceAsStreamFromSrc(resourcePath: List[String]): Option[java.io.InputStream] = { 32 | val classesDir = new File(getClass.getResource(".").toURI) 33 | val projectDir = classesDir.getParentFile.getParentFile.getParentFile.getParentFile 34 | val resourceFile = subFile(projectDir, ("src" :: "main" :: "resources" :: resourcePath): _*) 35 | if (resourceFile.exists) 36 | Some(new java.io.FileInputStream(resourceFile)) 37 | else 38 | None 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /w5/week5.actorbintree-instructions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasnake/Principles-of-Reactive-Programming/66b432473a81342569394c331fc092b8f39505de/w5/week5.actorbintree-instructions.pdf -------------------------------------------------------------------------------- /w6/kvstore/build.sbt: -------------------------------------------------------------------------------- 1 | submitProjectName := "kvstore" 2 | 3 | scalaVersion := "2.11.5" 4 | 5 | scalacOptions ++= Seq("-deprecation", "-feature") 6 | 7 | (fork in Test) := false 8 | 9 | projectDetailsMap := { 10 | val currentCourseId = "reactive-002" 11 | 12 | val depsNode = Seq( 13 | "io.reactivex" %% "rxscala" % "0.23.0", 14 | "io.reactivex" % "rxswing" % "0.21.0", // for Swing Scheduler in suggestions 15 | "org.json4s" %% "json4s-native" % "3.2.11", 16 | "org.scala-lang.modules" %% "scala-swing" % "1.0.1", 17 | "net.databinder.dispatch" %% "dispatch-core" % "0.11.0", 18 | "org.scala-lang" % "scala-reflect" % scalaVersion.value, 19 | "org.slf4j" % "slf4j-api" % "1.7.5", 20 | "org.slf4j" % "slf4j-simple" % "1.7.5", 21 | "com.squareup.retrofit" % "retrofit" % "1.0.0", 22 | "org.scala-lang.modules" %% "scala-async" % "0.9.2" 23 | ) 24 | 25 | val depsAkka = Seq( 26 | "com.typesafe.akka" %% "akka-actor" % "2.3.9", 27 | "com.typesafe.akka" %% "akka-testkit" % "2.3.9", 28 | "com.typesafe.akka" %% "akka-persistence-experimental" % "2.3.9" 29 | ) 30 | 31 | Map( 32 | "example" -> ProjectDetails( 33 | packageName = "example", 34 | assignmentPartId = "fTzFogNl", 35 | maxScore = 10d, 36 | styleScoreRatio = 0.0, 37 | courseId=currentCourseId), 38 | "quickcheck" -> ProjectDetails( 39 | packageName = "quickcheck", 40 | assignmentPartId = "02Vi5q7m", 41 | maxScore = 10d, 42 | styleScoreRatio = 0.0, 43 | courseId=currentCourseId, 44 | dependencies = Seq("org.scalacheck" %% "scalacheck" % "1.12.1")), 45 | "calculator" -> ProjectDetails( 46 | packageName = "calculator", 47 | assignmentPartId = "8uURtbi7", 48 | maxScore = 10d, 49 | styleScoreRatio = 0.0, 50 | courseId=currentCourseId), 51 | "nodescala" -> ProjectDetails( 52 | packageName = "nodescala", 53 | assignmentPartId = "RvoTAbRy", 54 | maxScore = 10d, 55 | styleScoreRatio = 0.0, 56 | courseId=currentCourseId, 57 | dependencies = depsNode), 58 | "suggestions" -> ProjectDetails( 59 | packageName = "suggestions", 60 | assignmentPartId = "rLLdQLGN", 61 | maxScore = 10d, 62 | styleScoreRatio = 0.0, 63 | courseId=currentCourseId), 64 | "actorbintree" -> ProjectDetails( 65 | packageName = "actorbintree", 66 | assignmentPartId = "VxIlIKoW", 67 | maxScore = 10d, 68 | styleScoreRatio = 0.0, 69 | courseId=currentCourseId, 70 | dependencies = depsAkka), 71 | "kvstore" -> ProjectDetails( 72 | packageName = "kvstore", 73 | assignmentPartId = "nuvh59Zi", 74 | maxScore = 20d, 75 | styleScoreRatio = 0.0, 76 | courseId=currentCourseId, 77 | dependencies = depsAkka) 78 | )} 79 | -------------------------------------------------------------------------------- /w6/kvstore/project/ReactiveBuild.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | import ch.epfl.lamp.CourseraBuild 4 | import ch.epfl.lamp.SbtCourseraPlugin.autoImport._ 5 | 6 | import org.scalajs.sbtplugin.ScalaJSPlugin 7 | import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._ 8 | 9 | object ProgfunBuild extends CourseraBuild { 10 | override def assignmentSettings: Seq[Setting[_]] = Seq( 11 | // This setting allows to restrict the source files that are compiled and tested 12 | // to one specific project. It should be either the empty string, in which case all 13 | // projects are included, or one of the project names from the projectDetailsMap. 14 | currentProject := "", 15 | 16 | // Packages in src/main/scala that are used in every project. Included in every 17 | // handout, submission. 18 | commonSourcePackages += "common", 19 | 20 | // Packages in src/test/scala that are used for grading projects. Always included 21 | // compiling tests, grading a project. 22 | 23 | libraryDependencies += "ch.epfl.lamp" %% "scala-grading-runtime" % "0.1", 24 | 25 | // Files that we hand out to the students 26 | handoutFiles <<= (baseDirectory, projectDetailsMap, commonSourcePackages) map { 27 | (basedir, detailsMap, commonSrcs) => 28 | (projectName: String) => { 29 | val details = detailsMap.getOrElse(projectName, sys.error("Unknown project name: "+ projectName)) 30 | val commonFiles = (PathFinder.empty /: commonSrcs)((files, pkg) => 31 | files +++ (basedir / "src" / "main" / "scala" / pkg ** "*.scala") 32 | ) 33 | val forAll = { 34 | (basedir / "src" / "main" / "scala" / details.packageName ** "*.scala") +++ 35 | commonFiles +++ 36 | (basedir / "src" / "main" / "resources" / details.packageName / "*") +++ 37 | (basedir / "src" / "test" / "scala" / details.packageName ** "*.scala") +++ 38 | (basedir / "build.sbt") +++ 39 | (basedir / "project" / "build.properties") +++ 40 | (basedir / "project" ** ("*.scala" || "*.sbt")) +++ 41 | (basedir / "project" / "scalastyle_config_reactive.xml") +++ 42 | (basedir / "lib_managed" ** "*.jar") +++ 43 | (basedir * (".classpath" || ".project")) +++ 44 | (basedir / ".settings" / "org.scala-ide.sdt.core.prefs") 45 | } 46 | if (projectName == "calculator") { 47 | forAll +++ 48 | (basedir / "webui.sbt") +++ 49 | (basedir / "web-ui" / "index.html") +++ 50 | (basedir / "web-ui" / "src" / "main" / "scala" ** "*.scala") 51 | } else 52 | forAll --- (basedir / "src" / "test" / "scala" / "kvstore" / "given" ***) 53 | } 54 | }) 55 | } 56 | -------------------------------------------------------------------------------- /w6/kvstore/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /w6/kvstore/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("ch.epfl.lamp" % "sbt-coursera" % "0.5") 2 | 3 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.2") 4 | -------------------------------------------------------------------------------- /w6/kvstore/src/main/scala/common/package.scala: -------------------------------------------------------------------------------- 1 | import java.io.File 2 | 3 | package object common { 4 | 5 | /** An alias for the `Nothing` type. 6 | * Denotes that the type should be filled in. 7 | */ 8 | type ??? = Nothing 9 | 10 | /** An alias for the `Any` type. 11 | * Denotes that the type should be filled in. 12 | */ 13 | type *** = Any 14 | 15 | 16 | /** 17 | * Get a child of a file. For example, 18 | * 19 | * subFile(homeDir, "b", "c") 20 | * 21 | * corresponds to ~/b/c 22 | */ 23 | def subFile(file: File, children: String*) = { 24 | children.foldLeft(file)((file, child) => new File(file, child)) 25 | } 26 | 27 | /** 28 | * Get a resource from the `src/main/resources` directory. Eclipse does not copy 29 | * resources to the output directory, then the class loader cannot find them. 30 | */ 31 | def resourceAsStreamFromSrc(resourcePath: List[String]): Option[java.io.InputStream] = { 32 | val classesDir = new File(getClass.getResource(".").toURI) 33 | val projectDir = classesDir.getParentFile.getParentFile.getParentFile.getParentFile 34 | val resourceFile = subFile(projectDir, ("src" :: "main" :: "resources" :: resourcePath): _*) 35 | if (resourceFile.exists) 36 | Some(new java.io.FileInputStream(resourceFile)) 37 | else 38 | None 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /w6/kvstore/src/main/scala/kvstore/Arbiter.scala: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import akka.actor.{ActorRef, Actor} 4 | import scala.collection.immutable 5 | 6 | object Arbiter { 7 | case object Join 8 | 9 | case object JoinedPrimary 10 | case object JoinedSecondary 11 | 12 | /** 13 | * This message contains all replicas currently known to the arbiter, including the primary. 14 | */ 15 | case class Replicas(replicas: Set[ActorRef]) 16 | } 17 | 18 | class Arbiter extends Actor { 19 | import Arbiter._ 20 | var leader: Option[ActorRef] = None 21 | var replicas = Set.empty[ActorRef] 22 | 23 | def receive = { 24 | case Join => 25 | if (leader.isEmpty) { 26 | leader = Some(sender) 27 | replicas += sender 28 | sender ! JoinedPrimary 29 | } else { 30 | replicas += sender 31 | sender ! JoinedSecondary 32 | } 33 | leader foreach (_ ! Replicas(replicas)) 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /w6/kvstore/src/main/scala/kvstore/Persistence.scala: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import akka.actor.{Props, Actor} 4 | import scala.util.Random 5 | import java.util.concurrent.atomic.AtomicInteger 6 | 7 | object Persistence { 8 | case class Persist(key: String, valueOption: Option[String], id: Long) 9 | case class Persisted(key: String, id: Long) 10 | 11 | class PersistenceException extends Exception("Persistence failure") 12 | 13 | def props(flaky: Boolean): Props = Props(classOf[Persistence], flaky) 14 | } 15 | 16 | class Persistence(flaky: Boolean) extends Actor { 17 | import Persistence._ 18 | 19 | def receive = { 20 | case Persist(key, _, id) => 21 | if (!flaky || Random.nextBoolean()) sender ! Persisted(key, id) 22 | else throw new PersistenceException 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /w6/kvstore/src/main/scala/kvstore/Replica.scala: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import akka.actor.{ OneForOneStrategy, Props, ActorRef, Actor } 4 | import kvstore.Arbiter._ 5 | import scala.collection.immutable.Queue 6 | import akka.actor.SupervisorStrategy.Restart 7 | import scala.annotation.tailrec 8 | import akka.pattern.{ ask, pipe } 9 | import akka.actor.Terminated 10 | import scala.concurrent.duration._ 11 | import akka.actor.PoisonPill 12 | import akka.actor.OneForOneStrategy 13 | import akka.actor.SupervisorStrategy 14 | import akka.util.Timeout 15 | 16 | object Replica { 17 | sealed trait Operation { 18 | def key: String 19 | def id: Long 20 | } 21 | case class Insert(key: String, value: String, id: Long) extends Operation 22 | case class Remove(key: String, id: Long) extends Operation 23 | case class Get(key: String, id: Long) extends Operation 24 | 25 | sealed trait OperationReply 26 | case class OperationAck(id: Long) extends OperationReply 27 | case class OperationFailed(id: Long) extends OperationReply 28 | case class GetResult(key: String, valueOption: Option[String], id: Long) extends OperationReply 29 | 30 | def props(arbiter: ActorRef, persistenceProps: Props): Props = Props(new Replica(arbiter, persistenceProps)) 31 | } 32 | 33 | class Replica(val arbiter: ActorRef, persistenceProps: Props) extends Actor { 34 | import Replica._ 35 | import Replicator._ 36 | import Persistence._ 37 | import context.dispatcher 38 | 39 | /* 40 | * The contents of this actor is just a suggestion, you can implement it in any way you like. 41 | */ 42 | 43 | var kv = Map.empty[String, String] 44 | // a map from secondary replicas to replicators 45 | var secondaries = Map.empty[ActorRef, ActorRef] 46 | // the current set of replicators 47 | var replicators = Set.empty[ActorRef] 48 | 49 | 50 | def receive = { 51 | case JoinedPrimary => context.become(leader) 52 | case JoinedSecondary => context.become(replica) 53 | } 54 | 55 | /* TODO Behavior for the leader role. */ 56 | val leader: Receive = { 57 | case _ => 58 | } 59 | 60 | /* TODO Behavior for the replica role. */ 61 | val replica: Receive = { 62 | case _ => 63 | } 64 | 65 | } 66 | 67 | -------------------------------------------------------------------------------- /w6/kvstore/src/main/scala/kvstore/Replicator.scala: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import akka.actor.Props 4 | import akka.actor.Actor 5 | import akka.actor.ActorRef 6 | import scala.concurrent.duration._ 7 | 8 | object Replicator { 9 | case class Replicate(key: String, valueOption: Option[String], id: Long) 10 | case class Replicated(key: String, id: Long) 11 | 12 | case class Snapshot(key: String, valueOption: Option[String], seq: Long) 13 | case class SnapshotAck(key: String, seq: Long) 14 | 15 | def props(replica: ActorRef): Props = Props(new Replicator(replica)) 16 | } 17 | 18 | class Replicator(val replica: ActorRef) extends Actor { 19 | import Replicator._ 20 | import Replica._ 21 | import context.dispatcher 22 | 23 | /* 24 | * The contents of this actor is just a suggestion, you can implement it in any way you like. 25 | */ 26 | 27 | // map from sequence number to pair of sender and request 28 | var acks = Map.empty[Long, (ActorRef, Replicate)] 29 | // a sequence of not-yet-sent snapshots (you can disregard this if not implementing batching) 30 | var pending = Vector.empty[Snapshot] 31 | 32 | var _seqCounter = 0L 33 | def nextSeq = { 34 | val ret = _seqCounter 35 | _seqCounter += 1 36 | ret 37 | } 38 | 39 | 40 | /* TODO Behavior for the Replicator. */ 41 | def receive: Receive = { 42 | case _ => 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /w6/kvstore/src/test/scala/kvstore/IntegrationSpec.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2013-2015 Typesafe Inc. 3 | */ 4 | package kvstore 5 | 6 | import akka.actor.{ Actor, Props, ActorRef, ActorSystem } 7 | import akka.testkit.{ TestProbe, ImplicitSender, TestKit } 8 | import org.scalatest.{ BeforeAndAfterAll, FlatSpec, Matchers } 9 | import scala.concurrent.duration._ 10 | import org.scalatest.FunSuiteLike 11 | import org.scalactic.ConversionCheckedTripleEquals 12 | 13 | class IntegrationSpec(_system: ActorSystem) extends TestKit(_system) 14 | with FunSuiteLike 15 | with Matchers 16 | with BeforeAndAfterAll 17 | with ConversionCheckedTripleEquals 18 | with ImplicitSender 19 | with Tools { 20 | 21 | import Replica._ 22 | import Replicator._ 23 | import Arbiter._ 24 | 25 | def this() = this(ActorSystem("ReplicatorSpec")) 26 | 27 | override def afterAll: Unit = system.shutdown() 28 | 29 | /* 30 | * Recommendation: write a test case that verifies proper function of the whole system, 31 | * then run that with flaky Persistence and/or unreliable communication (injected by 32 | * using an Arbiter variant that introduces randomly message-dropping forwarder Actors). 33 | */ 34 | } 35 | -------------------------------------------------------------------------------- /w6/kvstore/src/test/scala/kvstore/Step1_PrimarySpec.scala: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import akka.testkit.TestKit 4 | import akka.actor.ActorSystem 5 | import org.scalatest.FunSuiteLike 6 | import org.scalatest.BeforeAndAfterAll 7 | import org.scalatest.Matchers 8 | import akka.testkit.ImplicitSender 9 | import akka.testkit.TestProbe 10 | import scala.concurrent.duration._ 11 | import kvstore.Persistence.{ Persisted, Persist } 12 | import kvstore.Replica.OperationFailed 13 | import kvstore.Replicator.{ Snapshot } 14 | import scala.util.Random 15 | import scala.util.control.NonFatal 16 | import org.scalactic.ConversionCheckedTripleEquals 17 | 18 | class Step1_PrimarySpec extends TestKit(ActorSystem("Step1PrimarySpec")) 19 | with FunSuiteLike 20 | with BeforeAndAfterAll 21 | with Matchers 22 | with ConversionCheckedTripleEquals 23 | with ImplicitSender 24 | with Tools { 25 | 26 | override def afterAll(): Unit = { 27 | system.shutdown() 28 | } 29 | 30 | import Arbiter._ 31 | 32 | test("case1: Primary (in isolation) should properly register itself to the provided Arbiter") { 33 | val arbiter = TestProbe() 34 | system.actorOf(Replica.props(arbiter.ref, Persistence.props(flaky = false)), "case1-primary") 35 | 36 | arbiter.expectMsg(Join) 37 | } 38 | 39 | test("case2: Primary (in isolation) should react properly to Insert, Remove, Get") { 40 | val arbiter = TestProbe() 41 | val primary = system.actorOf(Replica.props(arbiter.ref, Persistence.props(flaky = false)), "case2-primary") 42 | val client = session(primary) 43 | 44 | arbiter.expectMsg(Join) 45 | arbiter.send(primary, JoinedPrimary) 46 | 47 | client.getAndVerify("k1") 48 | client.setAcked("k1", "v1") 49 | client.getAndVerify("k1") 50 | client.getAndVerify("k2") 51 | client.setAcked("k2", "v2") 52 | client.getAndVerify("k2") 53 | client.removeAcked("k1") 54 | client.getAndVerify("k1") 55 | } 56 | 57 | 58 | } 59 | -------------------------------------------------------------------------------- /w6/kvstore/src/test/scala/kvstore/Step2_SecondarySpec.scala: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import akka.testkit.{ TestProbe, TestKit, ImplicitSender } 4 | import org.scalatest.BeforeAndAfterAll 5 | import org.scalatest.Matchers 6 | import org.scalatest.FunSuiteLike 7 | import akka.actor.ActorSystem 8 | import scala.concurrent.duration._ 9 | import kvstore.Arbiter.{ JoinedSecondary, Join } 10 | import kvstore.Persistence.{ Persisted, Persist } 11 | import scala.util.Random 12 | import scala.util.control.NonFatal 13 | import org.scalactic.ConversionCheckedTripleEquals 14 | 15 | class Step2_SecondarySpec extends TestKit(ActorSystem("Step2SecondarySpec")) 16 | with FunSuiteLike 17 | with BeforeAndAfterAll 18 | with Matchers 19 | with ConversionCheckedTripleEquals 20 | with ImplicitSender 21 | with Tools { 22 | 23 | override def afterAll(): Unit = { 24 | system.shutdown() 25 | } 26 | 27 | test("case1: Secondary (in isolation) should properly register itself to the provided Arbiter") { 28 | val arbiter = TestProbe() 29 | val secondary = system.actorOf(Replica.props(arbiter.ref, Persistence.props(flaky = false)), "case1-secondary") 30 | 31 | arbiter.expectMsg(Join) 32 | } 33 | 34 | test("case2: Secondary (in isolation) must handle Snapshots") { 35 | import Replicator._ 36 | 37 | val arbiter = TestProbe() 38 | val replicator = TestProbe() 39 | val secondary = system.actorOf(Replica.props(arbiter.ref, Persistence.props(flaky = false)), "case2-secondary") 40 | val client = session(secondary) 41 | 42 | arbiter.expectMsg(Join) 43 | arbiter.send(secondary, JoinedSecondary) 44 | 45 | client.get("k1") should ===(None) 46 | 47 | replicator.send(secondary, Snapshot("k1", None, 0L)) 48 | replicator.expectMsg(SnapshotAck("k1", 0L)) 49 | client.get("k1") should ===(None) 50 | 51 | replicator.send(secondary, Snapshot("k1", Some("v1"), 1L)) 52 | replicator.expectMsg(SnapshotAck("k1", 1L)) 53 | client.get("k1") should ===(Some("v1")) 54 | 55 | replicator.send(secondary, Snapshot("k1", None, 2L)) 56 | replicator.expectMsg(SnapshotAck("k1", 2L)) 57 | client.get("k1") should ===(None) 58 | } 59 | 60 | test("case3: Secondary should drop and immediately ack snapshots with older sequence numbers") { 61 | import Replicator._ 62 | 63 | val arbiter = TestProbe() 64 | val replicator = TestProbe() 65 | val secondary = system.actorOf(Replica.props(arbiter.ref, Persistence.props(flaky = false)), "case3-secondary") 66 | val client = session(secondary) 67 | 68 | arbiter.expectMsg(Join) 69 | arbiter.send(secondary, JoinedSecondary) 70 | 71 | client.get("k1") should ===(None) 72 | 73 | replicator.send(secondary, Snapshot("k1", Some("v1"), 0L)) 74 | replicator.expectMsg(SnapshotAck("k1", 0L)) 75 | client.get("k1") should ===(Some("v1")) 76 | 77 | replicator.send(secondary, Snapshot("k1", None, 0L)) 78 | replicator.expectMsg(SnapshotAck("k1", 0L)) 79 | client.get("k1") should ===(Some("v1")) 80 | 81 | replicator.send(secondary, Snapshot("k1", Some("v2"), 1L)) 82 | replicator.expectMsg(SnapshotAck("k1", 1L)) 83 | client.get("k1") should ===(Some("v2")) 84 | 85 | replicator.send(secondary, Snapshot("k1", None, 0L)) 86 | replicator.expectMsg(SnapshotAck("k1", 0L)) 87 | client.get("k1") should ===(Some("v2")) 88 | } 89 | 90 | test("case4: Secondary should drop snapshots with future sequence numbers") { 91 | import Replicator._ 92 | 93 | val arbiter = TestProbe() 94 | val replicator = TestProbe() 95 | val secondary = system.actorOf(Replica.props(arbiter.ref, Persistence.props(flaky = false)), "case4-secondary") 96 | val client = session(secondary) 97 | 98 | arbiter.expectMsg(Join) 99 | arbiter.send(secondary, JoinedSecondary) 100 | 101 | client.get("k1") should ===(None) 102 | 103 | replicator.send(secondary, Snapshot("k1", Some("v1"), 1L)) 104 | replicator.expectNoMsg(300.milliseconds) 105 | client.get("k1") should ===(None) 106 | 107 | replicator.send(secondary, Snapshot("k1", Some("v2"), 0L)) 108 | replicator.expectMsg(SnapshotAck("k1", 0L)) 109 | client.get("k1") should ===(Some("v2")) 110 | } 111 | 112 | 113 | } 114 | -------------------------------------------------------------------------------- /w6/kvstore/src/test/scala/kvstore/Step3_ReplicatorSpec.scala: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import akka.testkit.{ TestProbe, TestKit, ImplicitSender } 4 | import org.scalatest.BeforeAndAfterAll 5 | import org.scalatest.Matchers 6 | import org.scalatest.FunSuiteLike 7 | import akka.actor.ActorSystem 8 | import scala.concurrent.duration._ 9 | import kvstore.Arbiter.{ JoinedSecondary, Join } 10 | import kvstore.Persistence.{ Persisted, Persist } 11 | import kvstore.Replicator.{ SnapshotAck, Snapshot, Replicate } 12 | import org.scalactic.ConversionCheckedTripleEquals 13 | 14 | class Step3_ReplicatorSpec extends TestKit(ActorSystem("Step3ReplicatorSpec")) 15 | with FunSuiteLike 16 | with BeforeAndAfterAll 17 | with Matchers 18 | with ConversionCheckedTripleEquals 19 | with ImplicitSender 20 | with Tools { 21 | 22 | override def afterAll(): Unit = { 23 | system.shutdown() 24 | } 25 | 26 | test("case1: Replicator should send snapshots when asked to replicate") { 27 | val secondary = TestProbe() 28 | val replicator = system.actorOf(Replicator.props(secondary.ref), "case1-replicator") 29 | 30 | replicator ! Replicate("k1", Some("v1"), 0L) 31 | secondary.expectMsg(Snapshot("k1", Some("v1"), 0L)) 32 | secondary.ignoreMsg({ case Snapshot(_, _, 0L) => true }) 33 | secondary.reply(SnapshotAck("k1", 0L)) 34 | 35 | replicator ! Replicate("k1", Some("v2"), 1L) 36 | secondary.expectMsg(Snapshot("k1", Some("v2"), 1L)) 37 | secondary.ignoreMsg({ case Snapshot(_, _, 1L) => true }) 38 | secondary.reply(SnapshotAck("k1", 1L)) 39 | 40 | replicator ! Replicate("k2", Some("v1"), 2L) 41 | secondary.expectMsg(Snapshot("k2", Some("v1"), 2L)) 42 | secondary.ignoreMsg({ case Snapshot(_, _, 2L) => true }) 43 | secondary.reply(SnapshotAck("k2", 2L)) 44 | 45 | replicator ! Replicate("k1", None, 3L) 46 | secondary.expectMsg(Snapshot("k1", None, 3L)) 47 | secondary.reply(SnapshotAck("k1", 3L)) 48 | } 49 | 50 | test("case2: Replicator should retry until acknowledged by secondary") { 51 | val secondary = TestProbe() 52 | val replicator = system.actorOf(Replicator.props(secondary.ref), "case2-replicator") 53 | 54 | replicator ! Replicate("k1", Some("v1"), 0L) 55 | secondary.expectMsg(Snapshot("k1", Some("v1"), 0L)) 56 | secondary.expectMsg(300.milliseconds, Snapshot("k1", Some("v1"), 0L)) 57 | secondary.expectMsg(300.milliseconds, Snapshot("k1", Some("v1"), 0L)) 58 | 59 | secondary.reply(SnapshotAck("k1", 0L)) 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /w6/kvstore/src/test/scala/kvstore/Step4_SecondaryPersistenceSpec.scala: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import akka.testkit.TestKit 4 | import akka.testkit.ImplicitSender 5 | import org.scalatest.BeforeAndAfterAll 6 | import org.scalatest.Matchers 7 | import org.scalatest.FunSuiteLike 8 | import akka.actor.ActorSystem 9 | import akka.testkit.TestProbe 10 | import scala.concurrent.duration._ 11 | import Arbiter._ 12 | import Persistence._ 13 | import org.scalactic.ConversionCheckedTripleEquals 14 | 15 | class Step4_SecondaryPersistenceSpec extends TestKit(ActorSystem("Step4SecondaryPersistenceSpec")) 16 | with FunSuiteLike 17 | with BeforeAndAfterAll 18 | with Matchers 19 | with ConversionCheckedTripleEquals 20 | with ImplicitSender 21 | with Tools { 22 | 23 | override def afterAll(): Unit = { 24 | system.shutdown() 25 | } 26 | 27 | test("case1: Secondary should not acknowledge snapshots until persisted") { 28 | import Replicator._ 29 | 30 | val arbiter = TestProbe() 31 | val persistence = TestProbe() 32 | val replicator = TestProbe() 33 | val secondary = system.actorOf(Replica.props(arbiter.ref, probeProps(persistence)), "case1-secondary") 34 | val client = session(secondary) 35 | 36 | arbiter.expectMsg(Join) 37 | arbiter.send(secondary, JoinedSecondary) 38 | 39 | client.get("k1") should ===(None) 40 | 41 | replicator.send(secondary, Snapshot("k1", Some("v1"), 0L)) 42 | val persistId = persistence.expectMsgPF() { 43 | case Persist("k1", Some("v1"), id) => id 44 | } 45 | 46 | withClue("secondary replica should already serve the received update while waiting for persistence: ") { 47 | client.get("k1") should ===(Some("v1")) 48 | } 49 | 50 | replicator.expectNoMsg(500.milliseconds) 51 | 52 | persistence.reply(Persisted("k1", persistId)) 53 | replicator.expectMsg(SnapshotAck("k1", 0L)) 54 | client.get("k1") should ===(Some("v1")) 55 | } 56 | 57 | test("case2: Secondary should retry persistence in every 100 milliseconds") { 58 | import Replicator._ 59 | 60 | val arbiter = TestProbe() 61 | val persistence = TestProbe() 62 | val replicator = TestProbe() 63 | val secondary = system.actorOf(Replica.props(arbiter.ref, probeProps(persistence)), "case2-secondary") 64 | val client = session(secondary) 65 | 66 | arbiter.expectMsg(Join) 67 | arbiter.send(secondary, JoinedSecondary) 68 | 69 | client.get("k1") should ===(None) 70 | 71 | replicator.send(secondary, Snapshot("k1", Some("v1"), 0L)) 72 | val persistId = persistence.expectMsgPF() { 73 | case Persist("k1", Some("v1"), id) => id 74 | } 75 | 76 | withClue("secondary replica should already serve the received update while waiting for persistence: ") { 77 | client.get("k1") should ===(Some("v1")) 78 | } 79 | 80 | // Persistence should be retried 81 | persistence.expectMsg(200.milliseconds, Persist("k1", Some("v1"), persistId)) 82 | persistence.expectMsg(200.milliseconds, Persist("k1", Some("v1"), persistId)) 83 | 84 | replicator.expectNoMsg(500.milliseconds) 85 | 86 | persistence.reply(Persisted("k1", persistId)) 87 | replicator.expectMsg(SnapshotAck("k1", 0L)) 88 | client.get("k1") should ===(Some("v1")) 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /w6/kvstore/src/test/scala/kvstore/Step5_PrimaryPersistenceSpec.scala: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import akka.testkit.TestKit 4 | import akka.testkit.ImplicitSender 5 | import org.scalatest.BeforeAndAfterAll 6 | import org.scalatest.Matchers 7 | import org.scalatest.FunSuiteLike 8 | import akka.actor.ActorSystem 9 | import scala.concurrent.duration._ 10 | import akka.testkit.TestProbe 11 | import Arbiter._ 12 | import Persistence._ 13 | import Replicator._ 14 | import org.scalactic.ConversionCheckedTripleEquals 15 | 16 | class Step5_PrimaryPersistenceSpec extends TestKit(ActorSystem("Step5PrimaryPersistenceSpec")) 17 | with FunSuiteLike 18 | with BeforeAndAfterAll 19 | with Matchers 20 | with ConversionCheckedTripleEquals 21 | with ImplicitSender 22 | with Tools { 23 | 24 | override def afterAll(): Unit = { 25 | system.shutdown() 26 | } 27 | 28 | test("case1: Primary does not acknowledge updates which have not been persisted") { 29 | val arbiter = TestProbe() 30 | val persistence = TestProbe() 31 | val primary = system.actorOf(Replica.props(arbiter.ref, probeProps(persistence)), "case1-primary") 32 | val client = session(primary) 33 | 34 | arbiter.expectMsg(Join) 35 | arbiter.send(primary, JoinedPrimary) 36 | 37 | val setId = client.set("foo", "bar") 38 | val persistId = persistence.expectMsgPF() { 39 | case Persist("foo", Some("bar"), id) => id 40 | } 41 | 42 | client.nothingHappens(100.milliseconds) 43 | persistence.reply(Persisted("foo", persistId)) 44 | client.waitAck(setId) 45 | } 46 | 47 | test("case2: Primary retries persistence every 100 milliseconds") { 48 | val arbiter = TestProbe() 49 | val persistence = TestProbe() 50 | val primary = system.actorOf(Replica.props(arbiter.ref, probeProps(persistence)), "case2-primary") 51 | val client = session(primary) 52 | 53 | arbiter.expectMsg(Join) 54 | arbiter.send(primary, JoinedPrimary) 55 | 56 | val setId = client.set("foo", "bar") 57 | val persistId = persistence.expectMsgPF() { 58 | case Persist("foo", Some("bar"), id) => id 59 | } 60 | // Retries form above 61 | persistence.expectMsg(200.milliseconds, Persist("foo", Some("bar"), persistId)) 62 | persistence.expectMsg(200.milliseconds, Persist("foo", Some("bar"), persistId)) 63 | 64 | client.nothingHappens(100.milliseconds) 65 | persistence.reply(Persisted("foo", persistId)) 66 | client.waitAck(setId) 67 | } 68 | 69 | test("case3: Primary generates failure after 1 second if persistence fails") { 70 | val arbiter = TestProbe() 71 | val persistence = TestProbe() 72 | val primary = system.actorOf(Replica.props(arbiter.ref, probeProps(persistence)), "case3-primary") 73 | val client = session(primary) 74 | 75 | arbiter.expectMsg(Join) 76 | arbiter.send(primary, JoinedPrimary) 77 | 78 | val setId = client.set("foo", "bar") 79 | persistence.expectMsgType[Persist] 80 | client.nothingHappens(800.milliseconds) // Should not fail too early 81 | client.waitFailed(setId) 82 | } 83 | 84 | test("case4: Primary generates failure after 1 second if global acknowledgement fails") { 85 | val arbiter = TestProbe() 86 | val persistence = TestProbe() 87 | val primary = system.actorOf(Replica.props(arbiter.ref, Persistence.props(flaky = false)), "case4-primary") 88 | val secondary = TestProbe() 89 | val client = session(primary) 90 | 91 | arbiter.expectMsg(Join) 92 | arbiter.send(primary, JoinedPrimary) 93 | arbiter.send(primary, Replicas(Set(primary, secondary.ref))) 94 | 95 | client.probe.within(1.second, 2.seconds) { 96 | val setId = client.set("foo", "bar") 97 | secondary.expectMsgType[Snapshot](200.millis) 98 | client.waitFailed(setId) 99 | } 100 | } 101 | 102 | test("case5: Primary acknowledges only after persistence and global acknowledgement") { 103 | val arbiter = TestProbe() 104 | val persistence = TestProbe() 105 | val primary = system.actorOf(Replica.props(arbiter.ref, Persistence.props(flaky = false)), "case5-primary") 106 | val secondaryA, secondaryB = TestProbe() 107 | val client = session(primary) 108 | 109 | arbiter.expectMsg(Join) 110 | arbiter.send(primary, JoinedPrimary) 111 | arbiter.send(primary, Replicas(Set(primary, secondaryA.ref, secondaryB.ref))) 112 | 113 | val setId = client.set("foo", "bar") 114 | val seqA = secondaryA.expectMsgType[Snapshot].seq 115 | val seqB = secondaryB.expectMsgType[Snapshot].seq 116 | client.nothingHappens(300.milliseconds) 117 | secondaryA.reply(SnapshotAck("foo", seqA)) 118 | client.nothingHappens(300.milliseconds) 119 | secondaryB.reply(SnapshotAck("foo", seqB)) 120 | client.waitAck(setId) 121 | } 122 | 123 | } 124 | -------------------------------------------------------------------------------- /w6/kvstore/src/test/scala/kvstore/Step6_NewSecondarySpec.scala: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import akka.testkit.TestKit 4 | import akka.testkit.ImplicitSender 5 | import org.scalatest.BeforeAndAfterAll 6 | import org.scalatest.Matchers 7 | import org.scalatest.FunSuiteLike 8 | import akka.actor.ActorSystem 9 | import akka.testkit.TestProbe 10 | import Arbiter._ 11 | import Replicator._ 12 | import org.scalactic.ConversionCheckedTripleEquals 13 | 14 | class Step6_NewSecondarySpec extends TestKit(ActorSystem("Step6NewSecondarySpec")) 15 | with FunSuiteLike 16 | with BeforeAndAfterAll 17 | with Matchers 18 | with ConversionCheckedTripleEquals 19 | with ImplicitSender 20 | with Tools { 21 | 22 | override def afterAll(): Unit = { 23 | system.shutdown() 24 | } 25 | 26 | test("case1: Primary must start replication to new replicas") { 27 | val arbiter = TestProbe() 28 | val primary = system.actorOf(Replica.props(arbiter.ref, Persistence.props(flaky = false)), "case1-primary") 29 | val user = session(primary) 30 | val secondary = TestProbe() 31 | 32 | arbiter.expectMsg(Join) 33 | arbiter.send(primary, JoinedPrimary) 34 | 35 | user.setAcked("k1", "v1") 36 | arbiter.send(primary, Replicas(Set(primary, secondary.ref))) 37 | 38 | secondary.expectMsg(Snapshot("k1", Some("v1"), 0L)) 39 | secondary.reply(SnapshotAck("k1", 0L)) 40 | 41 | val ack1 = user.set("k1", "v2") 42 | secondary.expectMsg(Snapshot("k1", Some("v2"), 1L)) 43 | secondary.reply(SnapshotAck("k1", 1L)) 44 | user.waitAck(ack1) 45 | 46 | val ack2 = user.remove("k1") 47 | secondary.expectMsg(Snapshot("k1", None, 2L)) 48 | secondary.reply(SnapshotAck("k1", 2L)) 49 | user.waitAck(ack2) 50 | } 51 | 52 | test("case2: Primary must stop replication to removed replicas and stop Replicator") { 53 | val arbiter = TestProbe() 54 | val primary = system.actorOf(Replica.props(arbiter.ref, Persistence.props(flaky = false)), "case2-primary") 55 | val user = session(primary) 56 | val secondary = TestProbe() 57 | 58 | arbiter.expectMsg(Join) 59 | arbiter.send(primary, JoinedPrimary) 60 | arbiter.send(primary, Replicas(Set(primary, secondary.ref))) 61 | 62 | val ack1 = user.set("k1", "v1") 63 | secondary.expectMsg(Snapshot("k1", Some("v1"), 0L)) 64 | val replicator = secondary.lastSender 65 | secondary.reply(SnapshotAck("k1", 0L)) 66 | user.waitAck(ack1) 67 | 68 | watch(replicator) 69 | arbiter.send(primary, Replicas(Set(primary))) 70 | expectTerminated(replicator) 71 | } 72 | 73 | test("case3: Primary must stop replication to removed replicas and waive their outstanding acknowledgements") { 74 | val arbiter = TestProbe() 75 | val primary = system.actorOf(Replica.props(arbiter.ref, Persistence.props(flaky = false)), "case3-primary") 76 | val user = session(primary) 77 | val secondary = TestProbe() 78 | 79 | arbiter.expectMsg(Join) 80 | arbiter.send(primary, JoinedPrimary) 81 | arbiter.send(primary, Replicas(Set(primary, secondary.ref))) 82 | 83 | val ack1 = user.set("k1", "v1") 84 | secondary.expectMsg(Snapshot("k1", Some("v1"), 0L)) 85 | secondary.reply(SnapshotAck("k1", 0L)) 86 | user.waitAck(ack1) 87 | 88 | val ack2 = user.set("k1", "v2") 89 | secondary.expectMsg(Snapshot("k1", Some("v2"), 1L)) 90 | arbiter.send(primary, Replicas(Set(primary))) 91 | user.waitAck(ack2) 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /w6/kvstore/src/test/scala/kvstore/Tools.scala: -------------------------------------------------------------------------------- 1 | package kvstore 2 | 3 | import akka.actor.ActorSystem 4 | import scala.concurrent.duration.FiniteDuration 5 | import akka.testkit.TestProbe 6 | import akka.actor.{ ActorRef, Actor } 7 | import org.scalatest.Matchers 8 | import org.scalatest.FunSuiteLike 9 | import akka.actor.Props 10 | import akka.testkit.TestKit 11 | import akka.testkit.ImplicitSender 12 | import scala.concurrent.duration._ 13 | 14 | object Tools { 15 | class TestRefWrappingActor(val probe: TestProbe) extends Actor { 16 | def receive = { case msg => probe.ref forward msg } 17 | } 18 | } 19 | 20 | /** 21 | * This is a utility to mix into your tests which provides convenient access 22 | * to a given replica. It will keep track of requested updates and allow 23 | * simple verification. See e.g. Step 1 for how it can be used. 24 | */ 25 | trait Tools { this: TestKit with FunSuiteLike with Matchers with ImplicitSender => 26 | 27 | import Arbiter._ 28 | import Tools._ 29 | 30 | def probeProps(probe: TestProbe): Props = Props(classOf[TestRefWrappingActor], probe) 31 | 32 | class Session(val probe: TestProbe, val replica: ActorRef) { 33 | import Replica._ 34 | 35 | @volatile private var seq = 0L 36 | private def nextSeq: Long = { 37 | val next = seq 38 | seq += 1 39 | next 40 | } 41 | 42 | @volatile private var referenceMap = Map.empty[String, String] 43 | 44 | def waitAck(s: Long): Unit = probe.expectMsg(OperationAck(s)) 45 | 46 | def waitFailed(s: Long): Unit = probe.expectMsg(OperationFailed(s)) 47 | 48 | def set(key: String, value: String): Long = { 49 | referenceMap += key -> value 50 | val s = nextSeq 51 | probe.send(replica, Insert(key, value, s)) 52 | s 53 | } 54 | 55 | def setAcked(key: String, value: String): Unit = waitAck(set(key, value)) 56 | 57 | def remove(key: String): Long = { 58 | referenceMap -= key 59 | val s = nextSeq 60 | probe.send(replica, Remove(key, s)) 61 | s 62 | } 63 | 64 | def removeAcked(key: String): Unit = waitAck(remove(key)) 65 | 66 | def getAndVerify(key: String): Unit = { 67 | val s = nextSeq 68 | probe.send(replica, Get(key, s)) 69 | probe.expectMsg(GetResult(key, referenceMap.get(key), s)) 70 | } 71 | 72 | def get(key: String): Option[String] = { 73 | val s = nextSeq 74 | probe.send(replica, Get(key, s)) 75 | probe.expectMsgType[GetResult].valueOption 76 | } 77 | 78 | def nothingHappens(duration: FiniteDuration): Unit = probe.expectNoMsg(duration) 79 | } 80 | 81 | def session(replica: ActorRef)(implicit system: ActorSystem) = new Session(TestProbe(), replica) 82 | 83 | 84 | } 85 | -------------------------------------------------------------------------------- /w6/week6.kvstore-instructions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vasnake/Principles-of-Reactive-Programming/66b432473a81342569394c331fc092b8f39505de/w6/week6.kvstore-instructions.pdf -------------------------------------------------------------------------------- /worksheets/build.sbt: -------------------------------------------------------------------------------- 1 | name := "ReactiveWorksheets" 2 | 3 | version := "1.0" 4 | 5 | scalaVersion := "2.11.8" 6 | 7 | val depsAkka = Seq( 8 | "com.typesafe.akka" %% "akka-actor" % "2.3.10", 9 | "com.typesafe.akka" %% "akka-testkit" % "2.3.10", 10 | "com.typesafe.akka" %% "akka-persistence-experimental" % "2.3.10", 11 | "com.ning" % "async-http-client" % "1.7.24", 12 | "org.jsoup" % "jsoup" % "1.8.3", 13 | "com.typesafe.akka" %% "akka-cluster" % "2.3.10" 14 | ) 15 | 16 | libraryDependencies ++= Seq( 17 | "com.netflix.rxjava" % "rxjava-scala" % "0.15.1", 18 | "org.scalatest" %% "scalatest" % "2.2.4", 19 | "junit" % "junit" % "4.11", 20 | "org.scala-lang.modules" %% "scala-async" % "0.9.2", 21 | "com.squareup.retrofit" % "retrofit" % "1.2.2" 22 | ) ++ depsAkka 23 | -------------------------------------------------------------------------------- /worksheets/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 0.13.8 -------------------------------------------------------------------------------- /worksheets/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := Level.Warn -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws01PartialFunctions.sc: -------------------------------------------------------------------------------- 1 | // case classes, JSON example 2 | // functions are objects 3 | // subclassing functions 4 | // partial matches 5 | // partial functions 6 | 7 | // case classes 8 | // JSON in Scala 9 | 10 | abstract class JSON 11 | 12 | case class JSeq(elems: List[JSON]) extends JSON 13 | 14 | case class JObj(bindings: Map[String, JSON]) extends JSON 15 | 16 | case class JNum(num: Double) extends JSON 17 | 18 | case class JStr(txt: String) extends JSON 19 | 20 | case class JBool(b: Boolean) extends JSON 21 | 22 | case object JNull extends JSON 23 | 24 | // JSON data example 25 | 26 | val data = JObj(Map( 27 | "firstName" -> JStr("John"), 28 | "lastName" -> JStr("Smith"), 29 | "address" -> JObj(Map( 30 | "streetAddress" -> JStr("21 2nd Street"), 31 | "state" -> JStr("NY") 32 | )) 33 | )) 34 | 35 | // pattern matching application 36 | 37 | def show(json: JSON): String = json match { 38 | case JSeq(elems) => "[" + (elems map show mkString ", ") + "]" 39 | case JObj(bind) => { 40 | val assocs = bind map { 41 | case (key, value) => "\"" + key + "\": " + show(value) 42 | } 43 | "{" + (assocs mkString ", ") + "}" 44 | } 45 | case JNum(num) => num.toString 46 | case JStr(str) => '\"' + str + '\"' 47 | case JBool(b) => b.toString 48 | case JNull => "null" 49 | } 50 | 51 | // functions are objects 52 | 53 | // function in 54 | /* 55 | val assocs = bind map { 56 | case (key, value) => "\"" + key + "\": " + show(value) 57 | } 58 | */ 59 | // what is the type of that function? 60 | // {case (key, value) => key + ": " + value} 61 | // taken by itself, the expression is not typable 62 | // expected type is 63 | // (String, Json) => String 64 | type JBinding = (String, JSON) 65 | // JBinding => String 66 | 67 | // function type 68 | type f1 = JBinding => String 69 | // is just a shorthand for type 70 | // scala.Function1[JBinding, String] 71 | // trait with its type arguments 72 | 73 | // so, pattern matching block 74 | // {case (key, value) => key + ": " + value} 75 | // expands to the Function1 instance 76 | new Function1[JBinding, String] { 77 | def apply(x: JBinding) = x match { 78 | case (key, value) => key + ": " + value 79 | } 80 | } 81 | 82 | // functions = classes, so we can subclassing functions 83 | // e.g. maps are functions 84 | trait _Map[key, Value] extends (key => Value) 85 | 86 | // or sequences are functions from Int to values 87 | trait _Seq[Elem] extends (Int => Elem) 88 | 89 | // so, seq(i) rewrites to 'apply' 90 | 91 | // partial matches 92 | 93 | // ok, PM block like 94 | // case "ping" => "pong" 95 | // can be given function type 96 | // String => String 97 | val f2: String => String = { 98 | case "ping" => "pong" 99 | } 100 | // but the function is not defined on all its domain! 101 | // f2("pong") // MatchError 102 | // is there a way to find out whether the function can be applied to a 103 | // given argument beforehand? 104 | 105 | // yes, there is: trait PartialFunction extends Function1 106 | val f3: PartialFunction[String, String] = { 107 | case "ping" => "pong" 108 | } 109 | f3.isDefinedAt("ping") // true 110 | f3.isDefinedAt("pong") // false 111 | 112 | // if the expected type is a PartialFunction 113 | // the Scala compiler will expand 114 | // { case "ping" => "pong" } 115 | // as follows 116 | new PartialFunction[String, String] { 117 | def apply(x: String) = x match { 118 | case "ping" => "pong" 119 | } 120 | 121 | def isDefinedAt(x: String) = x match { 122 | case "ping" => true 123 | case _ => false 124 | } 125 | } 126 | 127 | // exercise 128 | 129 | val f4: PartialFunction[List[Int], String] = { 130 | case Nil => "one" 131 | case x :: xs => xs match { 132 | case Nil => "two" 133 | } 134 | } 135 | f4.isDefinedAt(List(1, 2, 3)) // true 136 | f4(List(1, 2, 3)) // MatchError 137 | // PF can't control nested functions 138 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws02Collections.sc: -------------------------------------------------------------------------------- 1 | // recap: collections 2 | 3 | //core collections methods 4 | //mep, flatMap, filter 5 | //foldLeft, foldRight 6 | 7 | // idealized implementation of map on lists 8 | /* 9 | abstract class List[+T] { 10 | 11 | def map[U](func: T => U): List[U] = this match { 12 | case Nil => Nil 13 | case x :: xs => func(x) :: xs.map(func) 14 | } 15 | 16 | // flatMap 17 | def flatMap[U](func: T => List[U]): List[U] = this match { 18 | case Nil => Nil 19 | case x :: xs => func(x) ++ xs.flatMap(func) 20 | } 21 | 22 | // filter 23 | def filter(p: T => Boolean): List[T] = this match { 24 | case Nil => Nil 25 | case x :: xs => if(p(x)) x :: xs.filter(p) else xs.filter(p) 26 | } 27 | 28 | } 29 | */ 30 | 31 | val n = 5 32 | def isPrime(n: Int) = (2 until n) forall (d => n % d != 0) 33 | 34 | // for expressions 35 | 36 | // this ugly 2 nested loops 37 | (1 until n) flatMap (i => 38 | (1 until i) filter (j => 39 | isPrime(i + j)) map (j => (i, j))) 40 | 41 | // can be rewritten as 42 | for { 43 | i <- 1 until n 44 | j <- 1 until i 45 | if isPrime(i + j) 46 | } yield (i, j) 47 | // compiler will rewrite it back to flatMap, filter, map 48 | 49 | // rules 50 | // 1. 51 | // for(x <- e1) yield e2 52 | // to 53 | // e1.map( x => e2) 54 | // 2. 55 | // for (x <- e1 if f; s) yield e2 56 | // to 57 | // for (x <- e1.withFilter(x=>f); s) yield e2 58 | // 3. 59 | // for (x <- e1; y <- e2; s) yield e3 60 | // to 61 | // e1.flatMap(x => for(y <- e2; s) yield e3) 62 | 63 | // left-hand side of a generator may also be a pattern 64 | 65 | for { 66 | JObj(bindings) <- data 67 | JSeq(phones) = bindings("phoneNumbers") 68 | JObj(phone) <- phones 69 | JStr(digits) = phone("number") 70 | if digits startsWith "212" 71 | } yield (bindings("firstName"), bindings("lastName")) 72 | 73 | // pattern 'pat' with a single variable 'x' translated 74 | // pat <- expr 75 | // to 76 | x <- expr withFilter { 77 | case pat => true 78 | case _ => false 79 | } map { 80 | case pat => x 81 | } 82 | 83 | // exercise 84 | val N = 7 85 | for { 86 | x <- 2 to N 87 | y <- 2 to x 88 | if (x % y == 0) 89 | } yield (x, y) 90 | 91 | // expands to ? 92 | (2 to N) flatMap (x => 93 | (2 to x) withFilter (y => 94 | x % y == 0) map (y => (x, y))) 95 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws03ForQueries.sc: -------------------------------------------------------------------------------- 1 | implicit val pref = "log" 2 | def log(x: Any)(implicit pref: String) = println(s"${pref}: $x") 3 | log("for-expression")("queries") 4 | 5 | //> queries: for-expression 6 | 7 | // 'for' notation is essentially equivalent to the common 8 | // operations of DB query languages 9 | 10 | // example 11 | case class Book(title: String, authors: List[String]) 12 | 13 | val books = List( 14 | Book(title = "SICP", authors = List("Abelson, Harald", "Sussman, Gerald")), 15 | Book(title = "Effective Java", authors = List("Bloch, Joshua")), 16 | Book(title = "Java Puzzlers", authors = List("Bloch, Joshua"))) 17 | 18 | // find books whose author's name is 'Bloch' 19 | for { 20 | b <- books 21 | a <- b.authors 22 | if a contains "Bloch" 23 | } yield b.title 24 | 25 | // names of all authors with 2 books at least 26 | // author name repeats, not good 27 | for { 28 | b1 <- books 29 | b2 <- books 30 | if b1 != b2 31 | a1 <- b1.authors 32 | a2 <- b2.authors 33 | if a1 == a2 34 | } yield a1 35 | 36 | // can we avoid it? 37 | // combinations: 2, filter out: 1 38 | for { 39 | b1 <- books 40 | b2 <- books 41 | if b1.title < b2.title // check each book once 42 | a1 <- b1.authors 43 | a2 <- b2.authors 44 | if a1 == a2 45 | } yield a1 46 | 47 | // what if an author has published 3 books? 48 | val bkz = Book(title = "test", authors = List("Bloch, Joshua")) :: books 49 | 50 | // author is printed 3 times 51 | // combinations: 6, filter out: 3 52 | val ators = for { 53 | b1 <- bkz 54 | b2 <- bkz 55 | if b1.title < b2.title // check each book once 56 | a1 <- b1.authors 57 | a2 <- b2.authors 58 | if a1 == a2 59 | } yield a1 60 | 61 | // we can use 'distinct' 62 | ators.distinct 63 | // or, set: val books = Set ... like a real DB 64 | ators.toSet 65 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws04ForTranslations.sc: -------------------------------------------------------------------------------- 1 | implicit val pref = "log" //> pref : String = log 2 | def log(x: Any)(implicit pref: String) = println(s"${pref}: $x") 3 | log("for-expression")("translation to map, filter") 4 | 5 | // for-expressions is just a syntactic shugar 6 | // for map/flatMap/filter 7 | 8 | // syntax of 'for' is closely related to the higher-order functions 9 | // map, flatMap, filter 10 | { 11 | def map[T, U](xs: List[T], func: T => U): List[U] = 12 | for (x <- xs) yield func(x) 13 | 14 | def flatMap[T, U](xs: List[T], func: T => Iterable[U]): List[U] = 15 | for (x <- xs; y <- func(x)) yield y 16 | 17 | def filter[T](xs: List[T], p: T => Boolean): List[T] = 18 | for (x <- xs if p(x)) yield x 19 | 20 | "" 21 | } 22 | 23 | // only it goes the other way 24 | // for(x <- e1) yield e2 25 | // to 26 | // e1.map(x => e2) 27 | 28 | // for(x <- e1 if f; s) yield e2 29 | // to 30 | // for(x <- e1.withFilter(x => f); s) yield e2 31 | 32 | // for(x <- e1; y <- e2; s) yield e3 33 | // to 34 | // e1.flatMap(x => for(y <- e2; s) yield e3) 35 | 36 | // example 37 | val n = 3 38 | def isPrime(n: Int) = (2 until n) forall (x => n % x != 0) 39 | 40 | for { 41 | i <- 1 until n 42 | j <- 1 until i 43 | if isPrime(i + j) 44 | } yield (i, j) 45 | 46 | // translates to 47 | (1 until n).flatMap(i => 48 | (1 until i).withFilter(j => 49 | isPrime(i + j)).map(j => (i, j))) 50 | 51 | // exercise 52 | // translate next 'for' to higher-order functions 53 | 54 | case class Book(title: String, authors: List[String]) 55 | 56 | val books = List( 57 | Book(title = "SICP", authors = List("Abelson, Harald", "Sussman, Gerald")), 58 | Book(title = "Effective Java", authors = List("Bloch, Joshua")), 59 | Book(title = "Java Puzzlers", authors = List("Bloch, Joshua"))) 60 | 61 | for { 62 | b <- books 63 | a <- b.authors 64 | if a contains "Bloch" 65 | } yield b.title 66 | 67 | // translates to 68 | 69 | books.flatMap { 70 | b => 71 | b.authors.withFilter { 72 | a => a contains "Bloch" 73 | } map { 74 | a => b.title 75 | } 76 | } 77 | 78 | // any data type can be used in 'for' 79 | // if map, flatMap, withFilter defined 80 | 81 | // databased, XML, arrays, ... 82 | // ScalaQuery, Slick 83 | // MS LINQ 84 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws06Monads.sc: -------------------------------------------------------------------------------- 1 | import scala.util.Try 2 | import scala.util.control.NonFatal 3 | 4 | // type with flatMap and unit, obeying 3 laws: monad 5 | 6 | // a monad M is a parametric type M[T] 7 | // with 2 operations 8 | // flatMap, unit 9 | trait M[T] { 10 | def flatMap[U](func: T => M[U]): M[U] 11 | } 12 | def unit[T](x: T): M[T] 13 | 14 | // flatMap is called 'bind' 15 | 16 | // List is a monad, with unit(x) = List(x) 17 | // Option is a monad, with unit(x) = Some(x) 18 | // Generator is a monad, unit(x) = single(x) 19 | 20 | // map can be defined for every monad as a combination of 21 | // flatMap and unit 22 | /* 23 | m map f == m flatMap(x => unit(f(x))) 24 | == m flatMap (f andThen unit) 25 | */ 26 | 27 | // 3 laws of a monad 28 | // 1: associativity 29 | // m flatMap f flatMap g == m flatMap (x => f(x) flatMap g) 30 | // 2: left unit 31 | // unit(x) flatMap f == f(x) 32 | // 3: right unit 33 | // m flatMap unit == m 34 | 35 | // monad laws for Option 36 | abstract class Option[+T] { 37 | def flatMap[U](func: T => Option[U]): Option[U] = this match { 38 | case None => None 39 | case Some(x) => func(x) 40 | } 41 | } 42 | 43 | // check the left unit law 44 | // unit(x) flatMap f == f(x) 45 | Some(x) flatMap f == f(x) 46 | 47 | // right unit law 48 | // m flatMap unit == m 49 | opt flatMap Some == opt 50 | 51 | // associative law 52 | // m flatMap f flatMap g == m flatMap (x => f(x) flatMap g) 53 | opt flatMap f flatMap g == opt flatMap (x => f(x) flatMap g) 54 | == 55 | opt match {case Some(x) => f(x) case None => None } 56 | match {case Some(y) => g(y) case None => None } 57 | == 58 | opt match { 59 | case Some(x) => f(x) match {case Some(y) => g(y) ... } 60 | ... 61 | } 62 | == 63 | opt match { 64 | case Some(x) => f(x) flatMap g 65 | ... 66 | } 67 | 68 | // what these laws meaning? 69 | // associativity says essentially that one can 'inline' nested for-expr 70 | for(y <- for(x <- m; y <- f(x)) yield y 71 | z <- g(y)) yield z 72 | == 73 | for(x <- m; 74 | y <- f(x) 75 | z <- g(y)) yield z 76 | 77 | // right unit law says 78 | for (x <- m) yield x 79 | == 80 | m 81 | 82 | // left unit not used in for-expr 83 | 84 | // Try monad (like Option) 85 | // Success/Failure 86 | // actually, Try is not a monad: left unit law fails for Try (see below) 87 | 88 | abstract class Try[+T] 89 | case class Success[T](x: T) extends Try[T] 90 | case class Failure(ex: Exception) extends Try[Nothing] 91 | 92 | // for wrapping exceptions 93 | // note: Option wraps nulls 94 | 95 | // how to wrap? 96 | 97 | Try(expr) // Success(someVal) or Failure(someExc) 98 | 99 | // how Try is implemented? 100 | object Try { 101 | 102 | def apply[T](expr: => T): Try[T] = 103 | // can you see call-by-name parameter? 104 | try Success(expr) 105 | catch { case NonFatal(ex) => Failure(ex) } 106 | 107 | } 108 | 109 | // composing Try 110 | // it's the beauty of monads 111 | 112 | for { 113 | x <- computeX 114 | y <- computeY 115 | } yield f(x,y) 116 | // on success it will be Success(f(x,y)) 117 | // on error this will return Failure(ex) 118 | 119 | // for this we need map, flatMap 120 | abstract class Try[T] { 121 | 122 | def flatMap[U](func: T => Try[U]): Try[U] = this match { 123 | case fail: Failure => fail // do nothing on failed object 124 | case Success(x) => 125 | try func(x) 126 | catch { case NonFatal(ex) => Failure(ex) } 127 | } 128 | 129 | def map[U](func: T => U): Try[U] = this match { 130 | case fail: Failure => fail // do nothing on failed object 131 | case Success(x) => Try( func(x) ) 132 | } 133 | } 134 | 135 | // or, defining map using flatMap and unit 136 | t map f 137 | == 138 | t flatMap (x => Try(f(x))) 139 | == 140 | t flatMap (f andThan Try) 141 | 142 | // Try is not a monad, left unit law is fails 143 | // 2: left unit 144 | // unit(x) flatMap f == f(x) 145 | 146 | Try(expr) flatMap f != f(elxpr) 147 | // left-hand side will never rise a non-fatal exception 148 | // whereas right-hand side will 149 | 150 | // but, it's a win: 151 | // an expression composed from 'Try', 'map', 'flatMap' will never 152 | // throw a non-fatal exception 153 | 154 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws2.1State.sc: -------------------------------------------------------------------------------- 1 | //Functions and State (15:28) 2 | //https://class.coursera.org/reactive-002/lecture/47 3 | // 4 | //TOC 5 | //– for pure functions the concept of time is not important // no side effects 6 | //– this reflects on the Substitution Model of computation 7 | //– SM: rewrite, reduce expressions to values due to program evaluating; essential: function application rewriting. 8 | //– rewriting can be done anywhere in a term, all rewritings lead to the same solution – result of the lambda-calculus // confluence, aka: 9 | //– Church-Rosser theorem: the ordering in which the reductions are chosen does not make a difference to the eventual result 10 | //– it does not apply to stateful objects 11 | //– object has a state if its behaviour is influenced by its history; state changes over the course of time 12 | //– example: bank account 13 | //– state constructed from variables: var x = 'olala'; x = 'boo' // versus 'val' // change through an assignment 14 | //– class with variable members : BankAccoun … var balance ... 15 | //– statefulness connected with having variables; how strong? not strong at all. 16 | //– lets see, stream implementation using 'variable tail', not 'lazy val tail' – still not stateful. 17 | //– that implementation, just caching tail value, is not stateful, but variable was used allright 18 | //– another example, BankAccountProxy, w/o variable: is stateful because uses stateful BankAccoun object 19 | 20 | // in pure FP, w/o mutable state, the concept of time wasn't important 21 | 22 | // reminder: substitution model 23 | // program evaluating by rewriting 24 | 25 | // example 26 | def iterate(n: Int, func: Int => Int, x: Int): Int = 27 | if(n == 0) x else iterate(n-1, func, func(x)) 28 | 29 | def square(x: Int) = x*x 30 | 31 | iterate(1, square, 3) // 9 32 | 33 | // call gets rewritten as 34 | // take right-hand side, replace arguments 35 | if(1 == 0) 3 else iterate(1-1, square, square(3)) 36 | // as 37 | iterate(0, square, square(3)) 38 | // as 39 | iterate(0, square, 9) 40 | // as 41 | if(0 == 0) 9 else iterate(0-1, square, square(9)) 42 | 43 | // lambda-calculus: rewriting can be done anywhere in a term, 44 | // solution won't change from it 45 | // -- no state! 46 | 47 | // enters state, 48 | // an object has a state if its behaviour is influenced by its history 49 | // e.g bank account 50 | 51 | // every form of mutable state is constructed from variables 52 | var x: String = "abc" 53 | var count = 111 54 | 55 | // association name-value can be change later through an assignment 56 | count = count+1 57 | 58 | // state in objects 59 | class BankAccount { 60 | private var balance = 0 61 | 62 | def deposit(amount: Int) = 63 | if(amount > 0) balance += amount 64 | 65 | def withdraw(amount: Int): Int = 66 | if(0 < amount && amount <= balance) { 67 | balance -= amount 68 | balance 69 | } 70 | else throw new Error("insufficient funds") 71 | } 72 | 73 | val acc = new BankAccount 74 | acc deposit 50 75 | acc withdraw 20 76 | acc withdraw 20 77 | acc withdraw 25 // java.lang.Error: insufficient funds 78 | // same operations -- different result: depends on history 79 | 80 | // statefullness connected to having variables, right? 81 | // let's check it 82 | 83 | // Stream, replace lazy val with var 84 | def cons[T](hd: T, tl: => Stream[T]) = new Stream[T] { 85 | def head = hd 86 | 87 | private var tlOpt: Option[Stream[T]] = None 88 | 89 | def tail: T = tlOpt match { 90 | case Some(x) => x 91 | case None => tlOpt = Some(tl); tail 92 | } 93 | } 94 | // in that case, is the result of 'cons' a stateful object? 95 | // if you see it as a black box, then no, not stateful 96 | // behaviour don't depends on history 97 | 98 | // another example 99 | class BankAccountProxy(ba: BankAccount) { 100 | def deposit(amount: Int) = ba.deposit(amount) 101 | def withdraw(amount: Int) = ba.withdraw(amount) 102 | } 103 | // are instances of BankAccountProxy stateful objects? 104 | // yes, behaviour depends on history 105 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws2.2Identity.sc: -------------------------------------------------------------------------------- 1 | //Identity and Change (8:12) 2 | //https://class.coursera.org/reactive-002/lecture/31 3 | // 4 | //TOC 5 | //– are objects the same or different: identity 6 | //– referential transparency: val x = E; val y = E; == val x = E; val y = x // key property for substitution model 7 | //– is two BankAccoun the same? define 'being the same' 8 | //– property: operational equivalence – if no possible test can distinguish between them 9 | //– tests: arbitrary sequence of ops on (x, y) observing outcomes 10 | //– then, repeat, renaming 'y' by 'x' in tests code. if results are different = x and y are different 11 | //– ok, test operational equivalence for BankAccount : x deposit 30; y withdraw 20 // x deposit 30; x withdraw 20 – different 12 | //– that difference leads to : substitution model cannon be used 13 | //– SM can be adapted if we introduce a store, but this way to complicated 14 | //– good bye, substitution model 15 | 16 | // exclude assignments, get referential transparency 17 | val E = "some" 18 | 19 | { val x = E; val y = E } 20 | // x and y are the same 21 | // we could have also written 22 | { val x = E; val y = x } 23 | 24 | // but if assignment is allowed, we can't have referential transparency 25 | val x = new BankAccount 26 | val y = new BankAccount 27 | // x and y are different 28 | 29 | // what it means, to being the same 30 | 31 | // meaning is defined by the property of 32 | // operational equivalence 33 | 34 | // x and y are op.eq if 'no possible test' can distinguish 35 | // between them 36 | 37 | // to test if x and y are the same 38 | // execute arbitrary sequence 'f' of operations that involves x,y 39 | // observe outcomes 40 | { 41 | val x = new BankAccount 42 | val y = new BankAccount 43 | f(x, y) 44 | } 45 | // then, execute sequence, renaming y by x 46 | { 47 | val x = new BankAccount 48 | val y = new BankAccount 49 | f(x, x) 50 | } 51 | // if you see the difference, then expressions are different 52 | 53 | // example 54 | { // 1 half of the experiment 55 | val x = new BankAccount 56 | val y = new BankAccount 57 | x deposit 30 // 30 58 | y withdraw 20 // insufficient funds 59 | } 60 | { // 2 half of the experiment 61 | val x = new BankAccount 62 | val y = new BankAccount 63 | x deposit 30 // 30 64 | x withdraw 20 // 10 65 | } 66 | // you can see the difference 67 | 68 | // objects are the same 69 | { 70 | val x = new BankAccount 71 | val y = x 72 | } 73 | // but, to prove it, we should exec infinite number of operations 74 | 75 | // can't use substitution model in mutable world 76 | { 77 | val x = new BankAccount 78 | val y = x 79 | // and we rewrite y as 80 | { val y = new BankAccount } 81 | // no, we can't: it wont be the same object 82 | } 83 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws2.3Loops.sc: -------------------------------------------------------------------------------- 1 | //Loops (8:25) 2 | //https://class.coursera.org/reactive-002/lecture/35 3 | // 4 | //TOC 5 | //– another imperative prog. property: loop – control statement 6 | //– variables are enough to model all imperative programs? yes. 7 | //– while (i > 0) exp ; 'while' – keyword 8 | //– how to emulate loop using function? 9 | //– function WHILE using call-by-name params and tail-recursion 10 | //– function REPEAT emulate 'do-until' 11 | //– 'for' loop in Scala // similar to For-Expression 12 | //– for-loop translates using 'foreach' combinator, not map/flatMap 13 | 14 | // modeling imperative programs: state, control structures (loops) 15 | 16 | // how to emulate loop 17 | 18 | def power (x: Double, exp: Int): Double = { 19 | var r = 1.0 20 | var i = exp 21 | while(i > 0) { r = r*x; i = i - 1 } 22 | r 23 | } 24 | // while is a keyword 25 | 26 | // we can define 'while' using a function 27 | def _while(condition: => Boolean )(command: => Unit ): Unit = 28 | if(condition) { 29 | command 30 | _while(condition)(command) 31 | } 32 | else () 33 | // the condition and the command are call-by-name 34 | // so that they're reevaluated in each iteration 35 | // and, _while is tail recursive 36 | 37 | // what about repeat { command } ( condition ) 38 | def _repeat(command: => Unit)(condition: => Boolean): Unit = { 39 | command 40 | if (condition) () 41 | else _repeat(command)(condition) 42 | } 43 | 44 | // can you do this? 45 | // repeat { command } until ( condition ) 46 | 47 | // for-loop can't be modeled simply by a higher-order function 48 | // reason: in Java, for(int i = 1; i < 3; i = i+1) { do-someth ) 49 | // declared var i is visible in other arguments and in the body 50 | 51 | // in Scala we have another for-loop 52 | for(i <- 1 until 3) { System.out.print(i + " ") } 53 | // print 1 2 54 | 55 | // is it for-expression? 56 | // no, it is a for-loop 57 | 58 | // for-loops translate similarly to for-expr, but using 59 | // the foreach combinator instead of map/flatMap 60 | def foreach(func: T => Unit): Unit 61 | 62 | // example 63 | for(i <- 1 until 3; j <- "abc") println(i + " " + j) 64 | // translates to 65 | (1 until 3) foreach (i => "abc" foreach (j => println(i + " " + j))) 66 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws2.4DescreteEventSimulator1.sc: -------------------------------------------------------------------------------- 1 | //Extended Example: Discrete Event Simulation (Optional) (10:54) 2 | //https://class.coursera.org/reactive-002/lecture/37 3 | // 4 | //TOC 5 | //– application example, mutable variables as real world quantifiable properties model 6 | //– structure system as system of layers, DSL layers 7 | //– digital circuit simulator based on a framework for discrete event simulation 8 | //– change system state, on timer ticks: data model + event handlers/actions 9 | //– digital circuits: composed of wires, wires transport signals, signals transformed by components 10 | //– signals: true, false 11 | //– base components – gates: inverter, AND gate, OR gate 12 | //– component have a reaction time – delay ; connected by wires 13 | //– DC diagram for half-adder: sum and carry 14 | //– class Wire 15 | //– components as functions with side effects (to wires?): inverter, andGate, orGate – Wire in, Wire out 16 | //– construct function halfAdder from 4 Wires 17 | //– functon fullAdder with 5 params — wires: 2 halfAdders, 1 orGate 18 | //– function 'notEq': 3 params wires, 2 inverters, 2 andGates, 1 orGate 19 | 20 | //digital circuit simulator 21 | // based on framework for discrete event simulation 22 | // how assignments and higher-order functions can be combined 23 | 24 | //wires and functional components (gates) 25 | //– inverter: inverse of its input 26 | //– AND gate: conjunction of its inputs 27 | //– OR gate: disjunction 28 | 29 | // wires transport signals 30 | // signals transformed by components 31 | // signals: true/false 32 | 33 | // components have a reaction time, delay: 34 | // output don't change immediately 35 | 36 | //inverter (not) 37 | // in --^inv-- out 38 | 39 | // AND gate 40 | // in1 -- 41 | // &and-- out 42 | // in2 -- 43 | 44 | // OR gate 45 | // in1 -- 46 | // |or-- out 47 | // in2 -- 48 | 49 | // Half Adder HA 50 | // S = (a | b) & ^(a & b) // sum 51 | // C = a & b // carry 52 | 53 | // class Wire, connects components 54 | // for HA we have wires a,b,d,e 55 | class Wire 56 | 57 | // component as functions 58 | // as a side effect: creates a gate 59 | def inverter(input: Wire, output: Wire): Unit 60 | def andGate(inp1: Wire, inp2: Wire, output: Wire): Unit 61 | def orGate(inp1: Wire, inp2: Wire, output: Wire): Unit 62 | 63 | // half-adder as a function 64 | // a, b: input; s, c: output 65 | def halfAdder(a: Wire, b: Wire, s: Wire, c: Wire): Unit = { 66 | val d, e = new Wire 67 | orGate(a, b, d) // a|b = d 68 | andGate(a, b, c) // a&b = c 69 | inverter(c, e) // ^c = e 70 | andGate(d, e, s) // d&e = s 71 | } 72 | // propagate signal in order 73 | 74 | // full-adder 75 | // sum, cout: output 76 | // cin: carry in 77 | // cout: carry out 78 | // a, b, cin: input 79 | def fullAdder(a: Wire, b: Wire, cin: Wire, sum: Wire, cout: Wire): Unit = { 80 | val s, c1, c2 = new Wire 81 | halfAdder(b, cin, s, c1) // b, cin => ha => s, c1 82 | halfAdder(a, s, sum, c2) // a, s => ha => sum, c2 83 | orGate(c1, c2, cout) // c1|c2 = cout 84 | } 85 | 86 | // exercise 87 | // what logical function is it 88 | def func(a: Wire, b: Wire, c: Wire): Unit = { 89 | val d, e, f, g = new Wire 90 | inverter(a, d) // ^a = d 91 | inverter(b, e) // ^b = e 92 | andGate(a, e, f) // a&e = f 93 | andGate(b, d, g) // b&d = g 94 | orGate(f, g, c) // f&g = c 95 | } 96 | // f|g == (a&e) | (b&d) == (a & ^b) | (b & ^a) 97 | // 1,1 = 0 98 | // 0,0 = 0 99 | // 1,0 = 1 100 | // 0,1 = 1 101 | // a != b 102 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws2.5DescreteEventSimulator2.sc: -------------------------------------------------------------------------------- 1 | //Discrete Event Simulation: API and Usage (Optional) (10:57) 2 | //https://class.coursera.org/reactive-002/lecture/45 3 | // 4 | //TOC 5 | //– Wire, inverter, andGate, orGate: implementation based on API for discrete event simulation 6 | //– DES performs actions at a given moment; type Action = () => Unit // Unit => Unit 7 | //– the time is simulated 8 | //– trait Similation: currentTime, afterDelay, run 9 | //– class diagram for app: layers Simulation; Gates; Circuits; MySimulaton 10 | //– gates layer: Wire – getSignal, setSignal, addAction 11 | //– Wire implementation : state = value of the signal, list of actions 12 | //– Inverter implementation: installing an action on its input wire – inverse and output after a delay 13 | //– andGate/orGate is similar to Inverter 14 | 15 | //digital circuit simulator 16 | // based on framework for discrete event simulation 17 | // how assignments and higher-order functions can be combined 18 | 19 | // gates and wires implementation 20 | 21 | //discrete event simulation framework 22 | //simulator performs 'actions' specified by the user at a given 'moment' 23 | type Action = () => Unit 24 | // all work in side effects 25 | 26 | // the 'time' is simulated; it has nothing to do with the actual time 27 | 28 | // simulation happens inside an derived object 29 | // simulator API 30 | trait Simulation { 31 | // get current simulated time 32 | def currentTime: Int 33 | 34 | // register an action to perform after a delay 35 | def afterDelay(delay: Int)(block: => Action): Unit 36 | // can you see CBN parameter? 37 | 38 | // perform the simulation until there are no more actions waiting 39 | def run(): Unit 40 | } 41 | 42 | // simulator in flesh would be a objects hierarchy like 43 | // Simulation >: Gates >: Circuits >: MyCircuit01 44 | 45 | // Gates will consist of Wire class, 3 gates 46 | 47 | // Wire API 48 | // getSignal: Boolean 49 | // setSignal(sig: Boolean): Unit 50 | // addAction(a: Action): Unit 51 | // attached actions are executed at each change of the transported signal 52 | class Wire { 53 | private var sigVal = false // mutable state: default signal = 0 54 | private var actions: List[Action] = List() // attached gates, actually 55 | 56 | def getSignal: Boolean = sigVal 57 | 58 | // side effects (result: Unit) 59 | def setSignal(s: Boolean): Unit = if (s != sigVal) { 60 | sigVal = s 61 | actions foreach(_()) // propagate signal to attached gates 62 | } 63 | 64 | def addAction(a: Action): Unit = { 65 | actions = a :: actions 66 | a() // kick start 67 | } 68 | } 69 | 70 | // gates API 71 | 72 | // inverter 73 | // action on its input wire, after delay produce inverted signal in output wire 74 | def inverter (input: Wire, output: Wire): Unit = { 75 | 76 | def invertAction(): Unit = { 77 | val inSig = input.getSignal 78 | afterDelay(InverterDelay) { output setSignal !inSig } 79 | } 80 | 81 | input addAction invertAction 82 | } 83 | 84 | // AND gate 85 | def andGate(in1: Wire, in2: Wire, out: Wire): Unit = { 86 | 87 | def andAction(): Unit = { 88 | val in1Sig = in1.getSignal 89 | val in2Sig = in2.getSignal 90 | afterDelay(AndGateDelay) { out setSignal (in1Sig & in2Sig) } 91 | } 92 | 93 | in1 addAction andAction 94 | in2 addAction andAction 95 | } 96 | 97 | // OR gate 98 | def orGate(in1: Wire, in2: Wire, out: Wire): Unit = { 99 | 100 | def action(): Unit = { 101 | val in1Sig = in1.getSignal 102 | val in2Sig = in2.getSignal 103 | afterDelay(OrGateDelay) { out setSignal (in1Sig | in2Sig) } 104 | } 105 | 106 | in1 addAction action 107 | in2 addAction action 108 | } 109 | // n.b: getSignal get value from wire before delay 110 | // why? after dalay it can be different signal 111 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws2.7ClassicObserver.sc: -------------------------------------------------------------------------------- 1 | //Imperative Event Handling: The Observer Pattern (12:27) 2 | //https://class.coursera.org/reactive-002/lecture/107 3 | // 4 | //TOC 5 | //– user interface, classic event handlig: observer pattern 6 | //– views need to react to changes in a model : Observer pattern / publish-subscribe / MVC 7 | //– model: state of application 8 | //– view subscribe, model publish 9 | //– trait Publisher { var subscribers: Set[Subscriber]; def subscribe; def publish … 10 | //– trait Subscriber { def handler(p: Publisher) } 11 | //– example, class BankAccoun extends Publisher { … } 12 | //– class Consolidator(observed: List[BankAccount]) extends Subscriber { … } 13 | //– observer pattern, the good: view decoupled from state/model; any number of views; simple 14 | //– the bad: handlers are Unit-type, forces imperative style; many moving parts; 15 | //– don't fit in concurrency; views are still tightly bound to one state, update happens immediately. 16 | //– Adobe code (2008): 1/3 – event handling; ½ of the bugs 17 | //– bottom line: not good enough 18 | //– reactive: events => signals, messages, functions, futures, observables, actors 19 | 20 | // events, signals 21 | // FP magic pill: Feature 22 | // FRP: Functional Reactive Programming 23 | 24 | // Lecture 4.1 - Imperative Event Handling: The Observer Pattern 25 | // publish/subscribe, MVC 26 | 27 | // view need to react to changes in a model/state of an app 28 | // view subscribe on changes in a model 29 | // model publish its changes to subscribers 30 | 31 | trait Publisher { 32 | // mutable state! 33 | private var subscribers: Set[Subscriber] = Set() 34 | 35 | def subscribe(sub: Subscriber): Unit = 36 | subscribers += sub 37 | 38 | def unsubscribe(sub: Subscriber): Unit = 39 | subscribers -= sub 40 | 41 | def publish(): Unit = 42 | subscribers.foreach(_.handler(this)) 43 | } 44 | 45 | trait Subscriber { 46 | def handler(pub: Publisher): Unit = println("callback ...") 47 | } 48 | 49 | // pub/sub intertwined, not good 50 | 51 | // take a look to a BankAccount as a publisher 52 | class BankAccount extends Publisher { 53 | private var balance = 0 54 | 55 | // public getter for subscribers 56 | def currentBalance: Int = balance 57 | 58 | def deposit(amount: Int) = 59 | if(amount > 0) { 60 | balance += amount 61 | // call subscribers 62 | publish() 63 | } 64 | 65 | def withdraw(amount: Int): Unit = 66 | if(0 < amount && amount <= balance) { 67 | balance -= amount 68 | // call subscribers 69 | publish() 70 | } 71 | else throw new Error("insufficient funds") 72 | } 73 | 74 | // observer, view that will be called when bank acc changes 75 | class Consolidator(observed: List[BankAccount]) extends Subscriber { 76 | // add this to the list of subscribers in bank account 77 | observed.foreach(_.subscribe(this)) // call handler on acc change 78 | 79 | // mutable state! 80 | private var total: Int = _ // null 81 | compute() 82 | 83 | private def compute() = 84 | total = observed.map(_.currentBalance).sum 85 | 86 | override def handler(pub: Publisher) = compute() 87 | 88 | def totalBalance = total 89 | } 90 | 91 | // lets play 92 | val a, b = new BankAccount 93 | val c = new Consolidator(List(a,b)) 94 | c.totalBalance 95 | a deposit(20) 96 | c.totalBalance 97 | b deposit(30) 98 | c.totalBalance 99 | 100 | // observer pattern, good part 101 | // -- decouples views from state 102 | // -- allows to have any number of views 103 | // -- simple 104 | 105 | // bad part 106 | // -- forces imperative style, side effects 107 | // -- many parts that need to be co-ordinated 108 | // -- concurrency make things more complicated 109 | // -- views updates immediately; tightly bound to state 110 | 111 | // we can do better? 112 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws3.10Promise.sc: -------------------------------------------------------------------------------- 1 | //Promises, promises 2 | //https://class.coursera.org/reactive-002/lecture/133 3 | 4 | //TOC 5 | //Future, Promise 6 | //– Promise – a way to create Future creating value outside 7 | //– example: filter w/o async await 8 | //def filter(pred: T=> Boolean): Future [T] = { 9 | // val p = Promise[T]() 10 | // this onComplete { 11 | // case Failure(e) => p.failure(e) 12 | // case Success(x) => if( !pred(x) ) p.failure( new NoSuch … ) 13 | // else p.success(x) } 14 | // p.future } 15 | //trait Promise[T] { def future: Future[T]; def tryComplete(result: Try[T]): Boolean 16 | //– Promise is very imperative concept: like a mailbox with mutable variable: callback was executed 17 | //– like a variable where you get the result via callback 18 | //– example: racing 19 | // def race[T](left: Future[T], right: Future[T]): Future[T] = { 20 | // val p = Promise[T]() 21 | // left onComplete { p.tryComplete(_) } 22 | // right onComplete { p.tryComplete(_) } 23 | // p.future } 24 | //– helpers 25 | // trait Promise { def success(val: T): Unit = this.complete(Success(val)); def failure … 26 | //– example: zip using Promise 27 | // def zip[S, R](that: Future[S], f: (T, S) => R): Future[R] = { 28 | // val p = Promise[R]() 29 | // this onComplete { 30 | // case Failure(e) => p.failure(e); case Success(x) => that onComplete { 31 | // case Failure(e) => p.failure(e); case Success(y) => p.success(f(x, y)) } } 32 | // p.future } 33 | //– and with async await 34 | // def zip[S, R](that: Future[S], f: (T, S) => R): Future[R] = async { 35 | // f( await { this }, await { that } ) } 36 | //– much better 37 | //– recursive solution for Future list 38 | // def sequence[T](fts: List[Future[T]]): Future[List[T]] = { 39 | // fts match { 40 | // case Nil => Future(Nil) 41 | // case (ft::fts) => ft.flatMap( t => sequence(fts).flatMap(ts => Future(t::ts)) ) } } 42 | //– async await solution 43 | // def sequence[T](fs: List[Future[T]]): Future[List[T]] = async { 44 | // var _fs = fs ; val r = ListBuffer[T]() 45 | // while ( _fs != Nil ) { r += await { _fs.head } 46 | // _fs = _fs.tail } 47 | // r.toList } 48 | //– and using Promise ??? 49 | //done with effect: async Future // latency + failure 50 | 51 | //Promise – a way to create Future creating value outside 52 | // like a mailbox: create a mailbox and wait until somehow letter appears in it 53 | // like a mutable (once!) variable, very imperative concept 54 | // single assignment variable 55 | 56 | // example 57 | def filterII[T](future: Future[T], p: T => Boolean): Future[T] = { 58 | val p = Promise[T]() 59 | 60 | future.onComplete { // callback, get the value, async 61 | case Success(s) => { 62 | if(p(s)) p.success(s) // complete future 63 | else p.failure(new NoSuchElementException("No such element")) 64 | // success, failure: helpers for 'complete' 65 | } 66 | case Failure(f) => p.failure(f) 67 | } 68 | 69 | p.future // get future from promise 70 | // after some time it will be ready 71 | } 72 | 73 | // when Promise is useful? 74 | 75 | // situation: race, who came first? 76 | // example, very nice 77 | def race[T](left: Future[T], right: Future[T]): Future[T] = { 78 | val p = Promise[T]() 79 | 80 | left onComplete { p.tryComplete(_) } // try complete future 81 | right onComplete { p.tryComplete(_) } 82 | 83 | p.future // first actor 84 | } 85 | 86 | // when not to use Promise 87 | 88 | // example 89 | 90 | // async-await 91 | def zipI[T, S, R](future: Future[T], other: Future[S], combine: (T, S) => R): Future[R] = 92 | async { 93 | combine(await{ future }: T, await{ other }: S) 94 | } 95 | 96 | // promise // fugly 97 | def zipII[T, S, R](future: Future[T], other: Future[S], combine: (T, S) => R): Future[R] = { 98 | val p = Promise[R]() 99 | 100 | future onComplete { 101 | case Failure(f) => { p.failure(f) } 102 | case Success(t) => { other onComplete { 103 | case Failure(f) => { p.failure(f) } 104 | case Success(s) => p.success(combine(t,s)) 105 | }} 106 | } 107 | 108 | p.future 109 | } 110 | 111 | // when to use clear Future and flatMap 112 | 113 | // example 114 | def sequenceI[T](fts: List[Future[T]]): Future[List[T]] = fts match { 115 | case Nil => Future(Nil) 116 | case h::t => h.flatMap(a => sequence(t)) 117 | .flatMap(lst => Future(a::lst)) 118 | } 119 | 120 | // and async-await // fugly 121 | def sequenceII[T](fts: List[Future[T]]): Future[List[T]] = async { 122 | val r = ListBuffer[T]() 123 | 124 | var _fs = fts 125 | while(_fs != Nil) { 126 | r += await { _fs.head } 127 | _fs = _fs.tail 128 | } 129 | 130 | r.toList 131 | } 132 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws3.1Monads1.sc: -------------------------------------------------------------------------------- 1 | //Monads and Effects 1 2 | //https://class.coursera.org/reactive-002/lecture/115 3 | //Erik Meijer 4 | 5 | //TOC 6 | //– RxScala >= 0.23 7 | //– 'monad' == constructor + flatMap // vs 'unit', 'flatMap' and 3 laws: associativity, left unit, right unit. 8 | //– 4 essential effects in programming: (sync, async) * (one, many) 9 | //– Synchronous: one: T/Try[T]; many: Iterable[T] // monadic way 10 | //– Asynchronous: one: Future[T]; many: Observable[T] 11 | //– 2 cases: monad Try[T] for sync. exceptions; monad Future[T] for async. latency and exceptions. 12 | //– first effect: exceptions : we need to deal with failures: monad Try[T] 13 | //– example (sync): trait Adventure { def collectCoins(): List[Coin]; def buyTreasure(coins: List[Coin]): Treasure } 14 | //– not that simple: actions may fail: def collectCoins … throw new GameOverExeption(«eaten by monster»)... 15 | //– and sequential composition of actions may fail 16 | //– failures undeclared 17 | //– we need to expose possibility of failure 18 | //– replace 'T => S' by 'T => Try[S]' 19 | 20 | // toy example 21 | 22 | trait Adventure { 23 | def collectCoins(): List[Coin] = ??? 24 | def buyTreasure(coins: List[Coin]): Treasure = ??? 25 | } 26 | 27 | val adventure = Adventure() 28 | val coins = adventure.collectCoins() 29 | val treasure = adventure.buyTreasure(coins) 30 | 31 | // what if 'throw...'? actions may fail 32 | def collectCoins(): List[Coin] = { 33 | if (eatenByMonster) throw new GameOver("Ooops") 34 | List(Gold(), Gold(), Silver()) 35 | } 36 | // return type is a lie 37 | def buyTreasure(coins: List[Coin]): Treasure = { 38 | if (coins.sumBy(x => x.value) < treasureCost) 39 | throw new GameOver("Nice try!") 40 | Diamond() 41 | } 42 | // we need to declare it; we need a robust code 43 | 44 | // wrap type to a monad (railroad oriented programming: two inputs, two outputs 45 | // vs one input, two outputs) 46 | // T => S 47 | // T => Try[S] 48 | // may be Success, may be not: honestly expose possibility of failure 49 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws3.2Monads2.sc: -------------------------------------------------------------------------------- 1 | //Monads and Effects 2 2 | //https://class.coursera.org/reactive-002/lecture/117 3 | // 4 | //TOC 5 | //first effect: failures/exceptions. deal with failures using Try[T] monad 6 | //– express possible failure 7 | //– change 'T => S' to 'T => Try[S]' 8 | //– abstract class Try[T] … case class Success … extends Try[T] … case class Failure … extends Try[Nothing] 9 | //– trait Adventure … def collectCoins(): Try[List[Coin]] … 10 | //– dealing with failure explicitly: 11 | //val treasure = coins match { case Success … case failure@Failure(e) => failure } 12 | //– not so good looking 13 | //– will use high-order functions over Try (flatMap) – happy path, forget about exceptions 14 | //– it's possible because Try[T] is a monad! that handles exceptions 15 | //val treasure = adv.collectCoins().flatMap(coins => { adv.buyTreasure(coins) }) 16 | //or, using comprehension syntax 17 | //val treasure = for { coins ← adv.collectCoins(); treasure ← buyTreasure(coins) } yield treasure 18 | //– Try[T] design: flatMap and constructor 19 | //def map[S](f: T => S): Try[S] = this match { case Success(val) => Try(f(val)); case failure@Failure(t) => failure } 20 | //object Try { def apply[T](r: =>T): Try[T] = { try { Success(r) } catch { case t => Failure(t) } } 21 | 22 | abstract class Try[T] 23 | case class Success[T](elem: T) extends Try[T] 24 | case class Failure(t: Throwable) extends Try[Nothing] 25 | 26 | trait Adventure { 27 | def collectCoins(): Try[List[Coin]] = ??? 28 | def buyTreasure(coins: List[Coin]): Try[Treasure] = ??? 29 | } 30 | 31 | // two possible outcomes from actions, ok 32 | 33 | // this is ugly 34 | def PlayI(): Unit = { 35 | val adventure = Adventure() 36 | val coins: Try[List[Coin]] = adventure.collectCoins() 37 | val treasure: Try[Treasure] = coins match { 38 | case Success(cs) => adventure.buyTreasure(cs) 39 | case Failure(t) => Failure(t) 40 | } 41 | } 42 | 43 | // higher-order functions comes to resque 44 | // monad helps to take the happy path 45 | def PlayII(): Unit = { 46 | val adventure = Adventure() 47 | val coins: Try[List[Coin]] = adventure.collectCoins() 48 | val treasure: Try[Treasure] = 49 | coins.flatMap(cs => adventure.buyTreasure(cs)) 50 | } 51 | 52 | // or, using for-expression (comprehension) syntax 53 | def PlayIII(): Unit = { 54 | val adventure = Adventure() 55 | val treasure: Try[Treasure] = for { 56 | coins <- adventure.collectCoins() 57 | treasure <- buyTreasure(coins) 58 | } yield treasure 59 | } 60 | 61 | // how it works? 62 | trait Try[T] { 63 | def map[S](op: T => S): Try[S] = this match { 64 | case Success(value) => Try(op(value)) 65 | case failure@Failure(t) => failure 66 | } 67 | } 68 | object Try { 69 | def apply[T](expr: => T): Try[T] = { 70 | try Success(expr) 71 | catch { case t => Failure(t) } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws3.3Latency1.sc: -------------------------------------------------------------------------------- 1 | //Latency as an Effect 1 2 | //https://class.coursera.org/reactive-002/lecture/119 3 | // 4 | //TOC 5 | //– next effect: latency 6 | //– computation can take time : we need async: Future[T] 7 | //– Future[T] – monad for latency 8 | //– networking script: Adventure game => socket transfer 9 | //trait Socket { def readFromMemory(): Array[Byte]; def sendToEurope(packet: Array[Byte]): Array[Byte] 10 | //val sock = Socket(); val pack = sock.readFromMemory(); val confirmation = sock.sendToEurope(pack) 11 | //– time for ops: fetch from main mem = 100 nanosec; from L1 cache = 0.5 nanosec 12 | //send 2Kb over 1Gbps net = 20 000 nanosec 13 | //– readFromMemory: block for 50 000 ns, continue if there is no exception 14 | //– sendToEurope: block for 150 000 000 ns, continue if no exception 15 | //– in other scale: read = 3 day, send = 5 year 16 | //– express that latency, make it explicit 17 | //– allow non-blocking call, we can't wait 5 years: use callback 18 | //– ok, non-blocking actions: we need sequential composition of actions 19 | //– we need a monad for that composition 20 | 21 | // previous: exceptions as effect: computations can fail 22 | // wrap it into Try monad 23 | 24 | // now: computations can take time 25 | // wrap in into Future monad 26 | 27 | // previous toy example 28 | trait Coin { ??? } 29 | trait Treasure { ??? } 30 | trait Adventure { 31 | def collectCoins(): List[Coin] = ??? 32 | def buyTreasure(coins: List[Coin]): Treasure = ??? 33 | } 34 | val adventure = Adventure() 35 | val coins = adventure.collectCoins() 36 | val treasure = adventure.buyTreasure(coins) 37 | 38 | // morph this into network app 39 | trait Socket { 40 | def readFromMemory(): Array[Byte] 41 | def send2Europe(packet: Array[Byte]): Array[Byte] 42 | } 43 | 44 | val socket = Socket() 45 | val packet = socket.readFromMemory() 46 | val confirm = socket.send2Europe(packet) 47 | 48 | // takes a lot of time 49 | //val packet = socket.readFromMemory() 50 | //val confirm = socket.send2Europe(packet) 51 | 52 | // lets say: 1 nanosec = 1 sec 53 | // read 1 MB from RAM: 3 days 54 | // send packet from US to Europe and back: 5 years 55 | // and this ops can fail! 56 | 57 | // we want to express it explicitly 58 | // thus, we won't wait days and years, we wanna do some useful stuff 59 | // Async ops: call, bind callback, do next thing 60 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws3.4Latency2.sc: -------------------------------------------------------------------------------- 1 | import scala.collection.immutable.Queue 2 | import scala.util._ 3 | 4 | //Latency as an Effect 2 5 | //https://class.coursera.org/reactive-002/lecture/121 6 | 7 | //TOC 8 | //– non-blocking calls and sequential composition of actions: monad Future[T] 9 | //– Future[T] handles latency and exceptions! happy path 10 | //trait Future[T] { … def onComplete(callback: Try[T] => Unit)(implicit executor: ExecutionContext): Unit … 11 | //– callback needs to use pattern matching: case Success … case Failure … ? or use high-order functions? 12 | //– alternatives 13 | //trait Future[T] { def onComplete(success: T => Unit, failed: Throwable => Unit): Unit 14 | //def onComplete(callback: Observer[T]): Unit 15 | //trait Observer[T] { def on Next(value: T): Unit; def onError(err: Throwable): Unit … 16 | //– also it's called 'continuations' 17 | //– ok, app with Future looks like 18 | //trait Socket { def readFromMemory(): Future[Array[Byte]]; def sendToEurope(packet: Array[Byte]): Future[Array[Byte]] 19 | //– callback with match … case … seems ugly 20 | //– monad: happy path, flatMap .. next lecture 21 | //– creating Futures 22 | //object Future { def apply(body: =>T)(implicit context: ExecutionContext): Future[T] … 23 | //val q = Queue[EMailMessage](EmailMessage(from = 'me', to = 'you'), EmailMessage(... 24 | //def readFromMemory(): Future[Array[Byte]] = Future { 25 | //val em = q.dequeue(); var ser = serialization.findSerializerFor(em); ser.toBinary(em) } 26 | //– this code executes only once, even if we will register two (or more) callbacks 27 | 28 | // now: computations can take time 29 | // wrap it into Future monad 30 | 31 | // Future[T] 32 | // a monad that handles exceptions and latency 33 | // explicit latency 34 | { 35 | trait Future[T] { 36 | // essence: callback 37 | // will be called 'at most once' 38 | def onComplete(callback: Try[T] => Unit) 39 | } 40 | 41 | // callback need pattern matching but, 42 | // that design not only possible 43 | def myCallback(ts: Try[Int]): Unit = ts match { 44 | case Success(n) => onNext(n) 45 | case Failure(e) => onError(e) 46 | } 47 | // but, we can embed onNext & onError to some type 48 | } 49 | 50 | // alternative design, OO like 51 | 52 | trait Observer[T] { 53 | // two callbacks, aka continuations 54 | def onNext(value: T): Unit 55 | def onError(err: Throwable): Unit 56 | } 57 | // pass that to Future 58 | trait Future[T] { 59 | // two callbacks 60 | def onComplete(success: T => Unit, failed: Throwable => Unit): Unit 61 | // or, callback type 62 | def onComplete(callback: Observer[T]): Unit 63 | } 64 | 65 | // example, socket returns future 66 | 67 | trait Socket { 68 | def readFromMemory(): Future[Array[Byte]] 69 | def send2Europe(packet: Array[Byte]): Future[Array[Byte]] 70 | } 71 | 72 | val sock = Socket() 73 | val packet = sock.readFromMemory() // Future 74 | val confirm = packet.onComplete { // callback: Unit, we want Future 75 | case Success{p} => sock.send2Europe(p) // Future 76 | case Failure(t) => ??? // ??? 77 | } 78 | // isn't it looks nasty? yep 79 | // callbacks hell, a no-no, we need higher-order functions 80 | // monad.flatMap is our friend, see the next lecture: combinators on futures 81 | 82 | // for now lets talk about future construction 83 | 84 | // companion object 85 | object Future { 86 | // constructor, call-by-name expression with long-time computations 87 | def apply[T](body: => T) = ??? 88 | } 89 | 90 | // usage 91 | val queue = Queue[EMailMessage]( 92 | EMailMessage(from = "Erik", to = "Roland"), 93 | EMailMessage(from = "Martin", to = "Erik")) 94 | 95 | def readFromMemory(): Future[Array[Byte]] = 96 | Future { 97 | // body will be executed async, exactly once no matter how many callbacks 98 | val email = queue.dequeue() 99 | val serializer = serialization.findSerializerFor(email) 100 | serializer.toBinary(email) 101 | } 102 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws3.6FutureCombinators2.sc: -------------------------------------------------------------------------------- 1 | //Combinators on Futures 2 2 | //https://class.coursera.org/reactive-002/lecture/125 3 | 4 | //TOC 5 | //– Future monad, recovery combinator: fallbackTo 6 | //– def fallbackTo(that: =>Future[T]): Future[T] = { 7 | // if this future fails take the successful result of that future 8 | // if that future fails too, take the error of this future } 9 | //def sendToSafe(pack: Array): Future[Array] = 10 | // sendTo(mailServer.eur, pack) fallbackTo { 11 | // sendTo(mailServer.usa, pack) } recover { 12 | // case eurError => eurError.msg.toByteArray } 13 | //– implemented as 14 | //def fallbackTo(that: => Future[T]): Future[T] = { this recoverWith { 15 | // case _ => that recoverWith { case _ => this } } } 16 | //– beautiful 17 | //– async where possible, blocking where necessary 18 | //– two methods allow to block execution, don't do it ever 19 | //trait Awaitable … def ready … def result … 20 | //val packet: Future = sock.readFromMemory() 21 | //val confirm: Future = pack.flatMap(sock.sendToSafe(_)) 22 | //val debug = Await.result(confirm, 2 seconds); println(debug.toText) 23 | //– postfixOps 24 | //object Duration { def apply(len: Long, unit: TimeUnit): Duration } 25 | //val fiveYears = 1826 minutes 26 | //– combinators over Future[T] 27 | 28 | import java.net.URL 29 | import scala.concurrent.{Await, Future} 30 | import scala.concurrent.duration.{Duration, TimeUnit} 31 | 32 | // Lecture 4.7 - Combinators on Futures 2 33 | 34 | // example was: send packet resiliently 35 | // problem with error message 36 | trait Socket { 37 | 38 | def sendTo(url: URL, packet: Array[Byte]): Future[Array[Byte]] = 39 | Http(url, Request(packet)) 40 | .filter(response => response.isOK) 41 | .map(response => response.toByteArray) 42 | 43 | // if error, send to second server 44 | def sendToSafe(packet: Array[Byte]): Future[Array[Byte]] = 45 | // send to europe ... 46 | sendTo(mailServer.europe, packet) 47 | .recoverWith { 48 | case europeError => sendTo(mailServer.usa, packet) 49 | .recover { 50 | // get error message from usa, not really good 51 | case usaError => usaError.getMessage.toByteArray 52 | } 53 | } 54 | } 55 | // can we do better? 56 | 57 | // lets try to write a better recovery combinator 58 | { 59 | trait Future[T] { 60 | 61 | // with fallbackTo combinator we can write a nice code for Socket 62 | def fallbackTo(that: => Future[T]): Future[T] = { 63 | // if 'this' future fails, take the successful result of 'that' future ... 64 | // if 'that' future fails too, take the error of 'this' future 65 | this recoverWith { case _ => that recoverWith { case _ => this }} 66 | } // nice! 67 | 68 | def recover(func: PartialFunction[Throwable, T]): Future[T] 69 | def recoverWith(func: PartialFunction[Throwable, Future[T]]): Future[T] 70 | } 71 | 72 | // nice socket.sendToSafe 73 | trait Socket { 74 | 75 | def sendTo(url: URL, packet: Array[Byte]): Future[Array[Byte]] = 76 | Http(url, Request(packet)) 77 | .filter(response => response.isOK) 78 | .map(response => response.toByteArray) 79 | 80 | // if error, send to second server 81 | def sendToSafe(packet: Array[Byte]): Future[Array[Byte]] = 82 | // send to europe ... if-err: try usa ... if-err: message from europe 83 | sendTo(mailServer.europe, packet) 84 | .fallbackTo { sendTo(mailServer.usa, packet) } 85 | .recover { case europeError => europeError.getMessage.toByteArray } 86 | } 87 | } 88 | // good enough. 89 | 90 | // what else? 91 | 92 | // blocking methods, never-ever do this! 93 | // use it wisely, only if necessary, for debug, may be 94 | { 95 | trait Awaitable[T] extends AnyRef { 96 | // dangerous code, bloody murder!!! 97 | abstract def ready(atMost: Duration): Unit 98 | abstract def result(atMost: Duration): T 99 | } 100 | trait Future[T] extends Awaitable[T] { 101 | ??? 102 | } 103 | 104 | // blocking example 105 | val sock = Socket() 106 | val pack = sock.readFromMemory() // Future 107 | val confirm = pack.flatMap(sock.sendToSafe(_)) // Future 108 | // bloody murder!!! 109 | // block and wait up to 2 seconds 110 | val infomsg = Await.result(confirm, 2 seconds) 111 | println(infomsg.toText) 112 | } 113 | 114 | //aside: postfixOps 115 | { 116 | object Duration { 117 | def apply(length: Long, unit: TimeUnit): Duration = ??? 118 | } 119 | val fiveYears = 1826 minutes 120 | } 121 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws3.7FutureCompose1.sc: -------------------------------------------------------------------------------- 1 | //Composing Futures 1 2 | //https://class.coursera.org/reactive-002/lecture/127 3 | 4 | //TOC 5 | //– combinators over Future[T] : once we moved to future land we can't use regular control flow 6 | //– we need high-order functions over Future[T] to make life easier 7 | //– flatMap // for-comprehension 8 | //val packet: Future = sock.readFromMemory() 9 | //val confirm: Future = pack.flatMap(sock.sendToSafe(_)) 10 | //val confirm: Future = for { pack ← sock.readFromMemory(); confirm ← sock.sendToSafe(pack) } yield confirm 11 | //– lets write another combinator 12 | //def retry(nTimes: Int)(block: =>Future[T]): Future[T] = { 13 | // retry nTimes at most and give up } 14 | //– using recursion 15 | //if(nTimes == 0) { Future.failed(new Exception('oops')) } else { 16 | // block fallbackTo { retry(nTimes-1){ block } } } 17 | //– some people say: recursion is a GOTO for FP, use high-order functions! ... 18 | 19 | import scala.concurrent.{Awaitable, Future} 20 | 21 | // Lecture 4.8 - Composing Futures 1 22 | // for-expression on Future 23 | 24 | // control structures in Future land? no way 25 | // only higher-order functions, combinators 26 | 27 | // last version of example was 28 | { 29 | val sock = Socket() 30 | val pack = sock.readFromMemory() // Future 31 | val confirm = pack.flatMap(sock.sendToSafe(_)) // Future 32 | } 33 | 34 | // you could rewrite it in for-expr 35 | { 36 | val sock = Socket() 37 | val confirmation: Future[Array[Byte]] = for { 38 | pack <- sock.readFromMemory() 39 | confirm <- sock.send2Safe(pack) 40 | } yield confirm 41 | } 42 | 43 | // lets try another approach to enhance resilience 44 | { 45 | trait Future[T] extends Awaitable[T] { 46 | 47 | def retry(nTimes: Int)(block: => Future[T]): Future[T] = { 48 | // retry block at most nTimes 49 | // and give up after that 50 | if(nTimes <= 0) { 51 | Future.failed(new Exception("can't do")) 52 | } else { 53 | block fallbackTo { retry(nTimes-1){ block }} 54 | } 55 | } 56 | } 57 | } 58 | // can you do this w/o recursion? 59 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws3.9AsyncAwait.sc: -------------------------------------------------------------------------------- 1 | //Async Await 2 | // https://class.coursera.org/reactive-002/lecture/131 3 | 4 | //TOC 5 | //Future; get rid of it 6 | //– making effects implicit , in a limited scope, internally 7 | //– say: T => Future[S] 8 | //– mean: T => Try[S] or even T => S 9 | //– Async await magic 10 | //def async[T](body: =>T) (implicit context: ExecutionContext): Future[T] 11 | //def await[T](fut: Future[T]): T 12 | //async{ … await{ … } … } 13 | //– await allows to forget about Future and use type T 14 | //– illegal uses of await: under a try/catch; in expression passed as an argument to a by-name param, … 15 | //– lets taste it, example 16 | //def retry(nTimes: Int)(block: =>Future[T]): Future[T] = async { 17 | // var i = 0; var result: Try[T] = Failure(new Exception)) 18 | // while (result.isFailure && i < nTimes) { 19 | // result = await { Try(block); i += 1 } 20 | // result.get } 21 | //– example: Future filter 22 | // def filter(p: T => Boolean): Future[T] = async { 23 | // val x = await{ this } 24 | // if( !p(x) ) { throw new NoSuchElementException } 25 | // else { x } } 26 | //– example: flatMap 27 | // def flatMap[S](f: T=>Future[S]): Future[S] = async { 28 | // val x: T = await { this } 29 | // await { f(x) } } 30 | //– what about filter w/o await? Enters Promise // async await more preferrable 31 | // def filter(pred: T=> Boolean): Future [T] = { 32 | // val p = Promise[T]() 33 | // this onComplete { 34 | // case Failure(e) => p.failure(e) 35 | // case Success(x) => if( !pred(x) ) p.failure( new NoSuch … ) 36 | // else p.success(x) } 37 | // p.future } 38 | 39 | // Future, get rid of it 40 | // can we write things w/o boilerplate? 41 | 42 | // we want T => S, not T => Future[S] 43 | // make effect (latency) implicit 44 | { 45 | def async[T](body: => T): Future[T] = ??? 46 | def await[T](future: Future[T]): T = ??? 47 | 48 | val smth = async { ??? await { ??? } ... } 49 | } 50 | // a lots of limitations, but it will pay off 51 | 52 | // example 53 | def retry[T](nTimes: Int)(block: => Future[T]): Future[T] = 54 | async { // block return Future 55 | var i: Int = 0 56 | var result: Try[T] = Failure(new Exception("Oops")) 57 | 58 | while (i < nTimes && result.isFailure) { 59 | result = await { Try(block) } // Future[Try[T]], result is Try[T] 60 | i += 1 61 | } 62 | 63 | result.get 64 | } 65 | // call retry, get future and use it. 66 | 67 | // example 68 | def filterI[T](future: Future[T], p: T => Boolean): Future[T] = 69 | async{ 70 | val x: T = await{ future } 71 | if(!p(x)) { // predicate 72 | throw new NoSuchElementException("No such element") 73 | } else { 74 | x 75 | } 76 | } 77 | 78 | // example 79 | def flatMap[T,S](future: Future[T], op: T => Future[S]): Future[S] = 80 | async{ 81 | val x: T = await{ future } 82 | await{ op(x) }: S 83 | } 84 | 85 | // next: Promise 86 | // example 87 | def filterII[T](future: Future[T], p: T => Boolean): Future[T] = { 88 | val p = Promise[T]() 89 | 90 | future.onComplete { // callback 91 | case Success(s) => { 92 | if(p(s)) p.success(s) // set promise 93 | else p.failure(new NoSuchElementException("No such element")) 94 | } 95 | case Failure(f) => p.failure(f) 96 | } 97 | p.future // get future from promise 98 | } 99 | // you see: async ... await much better 100 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws4.2Iterables2Observables.sc: -------------------------------------------------------------------------------- 1 | //From Iterables to Observables 1 (8:06) 2 | //https://class.coursera.org/reactive-002/lecture/137 3 | 4 | //TOC 5 | //– second half of effects: Iterable and Observable 6 | //– trait Iterable is a base trait for all Scala collections: def iterator() : Iterator[T] 7 | //– trait Iterator { def hasNext: Boolean; def next(): T 8 | //– pull-based collections , synchronous 9 | //– Iterable is a monad! 10 | //– example, read files from disk (slow, sync) 11 | //def ReadLinesFromDisk(path: String): Iterator[String] = { 12 | //Source.fromFile(path).getLines() } 13 | //val lines = ReadLinesFromDisk('/tmp/foobar') 14 | //for (line <- lines) { ... } 15 | //– 2 weeks for line – no way, go async 16 | //– enters async streams 17 | //– convert the pull model into a push model 18 | //– first: simplify, get rid of 'hasNext' and change def next 19 | //trait Iterator[T] { ... def next(): Option[T] // Some[T] or None[Nothing], just like Try/Success/Failure 20 | //– trait with a single def = type 21 | //type Iterator[T] = () => Option[T] // func void/Unit => Option 22 | //– or, even more 23 | //trait Iterable[T] { def iterator(): ()=>Option[T] ... 24 | //– or, even more 25 | //type Iterable[T] = () => (() => Try[Option[T]]) 26 | //– and we get HO function 27 | //– lets flip the arrows, find duality 28 | 29 | // base trait for all Scala collections 30 | trait Iterable[T] { def iterator(): Iterator[T] } 31 | 32 | trait Iterator[T] { def hasNext: Boolean; def next(): T } 33 | 34 | //pull-based collections , synchronous 35 | 36 | // it's a monad 37 | def flatMap[B](op: A => Iterable[B]): Iterable[B] 38 | def map[B](op: A => B): Iterable[B] 39 | // constructor and other HO functions 40 | // ... and it's a functor (endofunctor) 41 | 42 | // example: slow sync reading 43 | 44 | def readLinesFromDisk(path: String): Iterator[String] = 45 | Source.fromFile(path).getLines() 46 | val lines = readLinesFromDisk("/dev/rnd") 47 | for (line <- lines) { 48 | println(line) 49 | } 50 | 51 | // can we derive async (dual) monad for Iterable? 52 | // duality: let's convert the pull model into a push model 53 | 54 | // rewrite things for simplicity 55 | { 56 | // remove hasNext, change T to Option[T] 57 | trait Iterable[T] { def iterator(): Iterator[T] } 58 | trait Iterator[T] { def next(): Option[T] } 59 | 60 | } 61 | { 62 | // trait with 1 function? it's a type 63 | type Iterator[T] = () => Option[T] 64 | } 65 | { 66 | // change iterable 67 | trait Iterable[T] { def iterator(): () => Option[T] } 68 | } 69 | { 70 | // again, trait with one func, rewrite it 71 | type Iterable[T] = () => (()=>Option[T]) 72 | // HO function 73 | } 74 | // next step: flip arrows 75 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws4.3Iterables2Observables2.sc: -------------------------------------------------------------------------------- 1 | //From Iterables to Observables 2 (9:44) 2 | //https://class.coursera.org/reactive-002/lecture/139 3 | 4 | //TOC 5 | //we need callbacks 6 | //– magic dualisation trick 7 | //type Iterable[T] = () => (() => Try[Option[T]]) 8 | //– factory of a factory to generate values of type T 9 | //– it's essence of the Iterable , sync 10 | //– ok, trick: flip the arrows 11 | //() => (() => Try[Option[T]]) 12 | //// flip 13 | //(Try[Option[T]] => Unit) => Unit 14 | //– seems like we need a callback around it, for push-based iterations 15 | //– complexify : callback that takes a cb that takes a value 16 | //type Observable[T] = (Try[Option[T]] => Unit) => Unit 17 | //– Try/Option require pattern matching, so lets pull out condition branches 18 | //type Observable[T] = (Throwable=>Unit, ()=>Unit, T=>Unit) => Unit 19 | //// 3 cases: exception, nothing, value 20 | //– encapsulate 3 cases into its own type : Observer 21 | //type Observable[T] = Observer[T] => Unit 22 | //type Observer[T] = (Throwable=>Unit, ()=>Unit, T=>Unit) 23 | //– observer will have 3 functions, make it trait 24 | //trait Observer[T] { 25 | // def onError(err: Throwable): Unit 26 | // def onCompleted(): Unit // empty collection 27 | // def onNext(value: T): Unit } 28 | //– observable becomes trait 29 | //trait Observable[T] { 30 | // def subscribe(observer: Observer[T]): Unit } 31 | //– and what it means? Observable get a callback 32 | //– callback have 3 cases-functions 33 | //– Observable vs. Future: obs.callback on each call get another value; future get the same value 34 | //– little more: subscribe returns subscription 35 | //trait Observable[T] { def subscribe(observer: Observer[T]): Subscription } 36 | //trait Subscription { def unsubscribe...; defisUnsubscribed... } 37 | //– Iterable and Observable are dual 38 | //– we had seen all 4 effects, connected via category theory 39 | 40 | import scala.util.Try 41 | 42 | // continue flipping arrows: Iterable to Observable derivation 43 | // magic of Category Theory 44 | 45 | type Iterable[T] = 46 | () => (() => Try[Option[T]]) 47 | // factory of factories of values :) 48 | // 3 kind of values: exception, none, some 49 | 50 | // flip the arrows, get a callback (push-based type) 51 | type Observable[T] = 52 | (Try[Option[T]] => Unit) => Unit 53 | // setter of a setter 54 | 55 | // and complixify it back, grow functions to full-size traits 56 | 57 | // unwrap Try[Option[T]] to 3 cases 58 | type Observable[T] = 59 | (Throwable=>Unit, ()=>Unit, T=>Unit) => Unit 60 | 61 | // move 3 functions to type 62 | type Observer[T] = (Throwable=>Unit, ()=>Unit, T=>Unit) 63 | // then 64 | type Observable[T] = Observer[T] => Unit 65 | 66 | // move to trait 67 | 68 | trait Observer[T] { // vs Iterator 69 | def onError(err: Throwable): Unit 70 | def onCompleted(): Unit // empty collection, EOF 71 | def onNext(value: T): Unit 72 | } 73 | 74 | trait Observable[T] { // vs Iterable 75 | def subscribe(observer: Observer[T]): Subscription 76 | } 77 | // observable: something that I can feed a callback 78 | // cb with 3 functions, all side-effected 79 | 80 | // each time cb is called, it have a new data (vs Future) 81 | 82 | // subscription can be cancelled (cancellation token) 83 | // callback can be removed 84 | trait Subscription { 85 | def unsubscribe(): Unit 86 | def isUnsubscribed: Boolean 87 | } 88 | 89 | // n.b. Iterable and Observable are dual 90 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws4.4ObservablesExample.sc: -------------------------------------------------------------------------------- 1 | //Hello World Observables (6:29) 2 | //https://class.coursera.org/reactive-002/lecture/141 3 | 4 | //TOC 5 | //– simple example, observables 6 | //– stream of values, every 1 sec.; filter them; group in chunks of 2, shifted by 1; print 7 | //val ticks: Observable[Long] = Observable.interval(1 seconds) 8 | //val evens: Observable[Long] = ticks.filter(_ % 2 == 0) 9 | //val bufs = Observable[Seq[Long]] = evens.slidingBuffer(count=2, skip=1) 10 | //val s = bufs.subscribe(println(_)) 11 | //.... 12 | //s.unsubscribe() 13 | //– marble diagram 14 | // 15 | 16 | import rx.lang.scala.{Observable, Subscription} 17 | import scala.language.postfixOps 18 | import scala.concurrent.duration._ 19 | 20 | def ticks(): Unit = { 21 | 22 | val ticks: Observable[Long] = Observable.interval(1 second) 23 | // Observable is a collection monad! 24 | val evens: Observable[Long] = ticks.filter(s => s%2 == 0) 25 | val buffers: Observable[Seq[Long]] = evens.buffer(2, 1) 26 | 27 | // run the program for a while 28 | val subscription: Subscription = buffers.subscribe(println(_)) 29 | 30 | readLine() 31 | 32 | // stop the stream 33 | subscription.unsubscribe() 34 | } 35 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws4.6Subscriptions.sc: -------------------------------------------------------------------------------- 1 | import rx.lang.scala.subscriptions.{CompositeSubscription, MultipleAssignmentSubscription} 2 | 3 | //Subscriptions (10:34) 4 | //https://class.coursera.org/reactive-002/lecture/145 5 | 6 | //TOC 7 | //– what happens when x.unsubscribe? (x = q.subscribe; y = q.subscribe ) 8 | //– x will not receive any items but y still 'live' 9 | //– subscription causes side effect: each subscriber has its own source == Cold Observable 10 | //– Hot Observable: same source shared by all subscribers, subscription w/o side effect 11 | //– unsubscribing != cancellation : unsubscribing don't cancel stream or other subscriptions 12 | //– subscriptions form an interesting algebra (state: isUnsubscribed; ops: unsubscribe, subscribe; collection) 13 | //collection of subscriptions, unsubscribe all it once: CompositeSubscription 14 | //MultiAssignmentSubscription: allow replace/swap subscription source 15 | // SerialSubscription: subscribing to next source if unsubscribe from prev. 16 | //– idempotent subscriptions: call 'unsubscribe' many times – action only once 17 | //– unsubscribe composite: all subs in collection will be unsubscribed 18 | //– adding sub to unsubscribed composite: sub will be unsubscribed 19 | //– also multi 20 | //– unsubscribe inner item do nothing with collection / has no effect on container 21 | //– subscriptions used in observables in ops implementations 22 | //– this algebra used inside other ops 23 | 24 | // unsubscribing 25 | 26 | val quakes: Observable[EarthQuake] = ??? 27 | val s1: Subscription = quakes.Subscribe(f1) 28 | val s2: Subscription = quakes.Subscribe(f2) 29 | 30 | s1.unsubscribe() 31 | // what happens to s2? 32 | // s2 keep recieving messages 33 | // unsubscribing != cancellation 34 | 35 | // cold vs hot observables 36 | 37 | // cold observable: each subscriber has its own source 38 | // subscription side-effect: create a new source 39 | 40 | // hot observable: same source shared by all subscribers 41 | 42 | // basics 43 | 44 | trait Subscription { 45 | def unsubscribe(): Unit 46 | def isUnsubscribed: Boolean 47 | } 48 | object Subscription { 49 | def apply(unsubscribe: => Unit): Subscription = ??? 50 | } 51 | 52 | // unsubscribe is idempotent: can't break anything by repeatedly calling 53 | // unsubscribe 54 | 55 | // Subscription algebra 56 | // 3 kinds of subscriptions 57 | 58 | // collections of subscriptions, unsubscribe alltogether 59 | class CompositeSubscription extends Subscription { 60 | def +=(s: Subscription): this.type 61 | def -=(s: Subscription): this.type 62 | } 63 | 64 | // swap underlying subscription 65 | class MultiAssignmentSubscription extends Subscription { 66 | def subscription: Subscription 67 | def subscription_=(that: Subscription): this.type 68 | } 69 | 70 | // special case of MultiAssignment: unsubscribed when swapping 71 | class SerialSubscription extends Subscription { 72 | def subscription: Subscription 73 | def subscription_=(that: Subscription): this.type 74 | } 75 | 76 | // composite demo 77 | val a = Subscription { println("A") } 78 | val b = Subscription { println("B") } 79 | val c = Subscription { println("C") } 80 | val composite = CompositeSubscription(a, b) 81 | 82 | println(composite.isUnsubscribed) // no 83 | composite.unsubscribe() 84 | println(composite.isUnsubscribed) // yes 85 | println(a.isUnsubscribed) // yes 86 | println(c.isUnsubscribed) // no 87 | composite += c 88 | println(c.isUnsubscribed) // yes 89 | 90 | // multi demo 91 | val multi = MultipleAssignmentSubscription() 92 | multi.subscription = a 93 | multi.subscription = b 94 | multi.unsubscribe() // b unsubscribe 95 | multi.subscription = c // c unsubscribe 96 | 97 | // subscriptions algebra is used not in client code, but in operators/combiners 98 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws4.7ObservablesSubjects.sc: -------------------------------------------------------------------------------- 1 | import rx.lang.scala.subjects.{PublishSubject, ReplaySubject} 2 | 3 | import scala.concurrent.Promise 4 | 5 | //Promises and Subjects (8:55) 6 | //https://class.coursera.org/reactive-002/lecture/147 7 | 8 | //TOC 9 | //– Promise for Future → Subjects for Observables 10 | //– Promise for Future – promise 'hasa' future 11 | //def map[S](f: t => S)(implicit context): Future[S] = { 12 | // val p = Promise[S](); this.onComplete { 13 | // case t => try { p.success(f(t)) } catch { case e => p.failure(e) } } 14 | // p.future } 15 | //– Subject for Observable – subject 'isa' observable 16 | //– subject do nothing if you call 'onComleted' twice, but promise throw error 17 | //– subject are like channel: on one side pump in values, on the other – subscribers get data 18 | //– subject are like combination of observer and observable 19 | //val channel = PublishSubject[Int]() 20 | //val a = channel.subscribe(x => println(s'a: $x')) 21 | //val b = channel.subscribe(x => println(s'b: $x')) 22 | //channel.onNext(42) 23 | //a.unsubscribe() 24 | //channel.onNext(4711) 25 | //channel.onCompleted() 26 | //val c = channel.subscribe(x => println(s'c: $x')) 27 | //channel.onNext(13) 28 | //– in that example sub 'c' only get empty observable, but 29 | //– ReplaySubject: has history/memory, caches all values – sub 'c' will get all values 30 | //– other subjects: 31 | // async subject: caches final value 32 | //behaviour subj: caches latest value 33 | //– bad subjects: like mutable variables; not work well in backpressure; 34 | //– in most cases you don't need subjects 35 | 36 | // creating streams, Subject vs Promise 37 | 38 | // promise works like this 39 | def map[T, S](fut: Future[T], op: T => S): Future[S] = { 40 | val p = Promise[S]() 41 | 42 | fut.onComplete { // delayed future complete 43 | case t => 44 | try { p.success(op(t)) } 45 | catch { case e => p.failure(e) } 46 | } 47 | 48 | p.future 49 | } 50 | // promise 'hasa' future 51 | 52 | // subject 'isa' observable, like a channel in-out 53 | // you can push values to channel (like complete future in promise) 54 | 55 | trait Subject[T] { 56 | // Observer 57 | def onNext 58 | def onCompleted 59 | def onError 60 | 61 | // Observable 62 | def subscribe 63 | } 64 | 65 | // kinds of subjects 66 | { 67 | val channel = PublishSubject[Int]() // simplest 68 | val a = channel.subscribe(x => println(s"a: $x")) // 42 69 | val b = channel.subscribe(x => println(s"b: $x")) // 42, 4711, ! 70 | channel.onNext(42) // push value into channel 71 | a.unsubscribe() 72 | channel.onNext(4711) 73 | channel.onCompleted() // close ! 74 | val c = channel.subscribe(x => println(s"c: $x")) // ! 75 | channel.onNext(13) // dropped on the floor 76 | } 77 | { 78 | val channel = ReplaySubject[Int]() // has memory 79 | val a = channel.subscribe(x => println(s"a: $x")) // 42 80 | val b = channel.subscribe(x => println(s"b: $x")) // 42, 4711, ! 81 | channel.onNext(42) 82 | a.unsubscribe() 83 | channel.onNext(4711) 84 | channel.onCompleted() // close ! 85 | val c = channel.subscribe(x => println(s"c: $x")) // 42, 4711, ! 86 | channel.onNext(13) // dropped on the floor 87 | } 88 | // others: async, behaviour 89 | 90 | // try not to use subjects, it's imperative 91 | 92 | 93 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws4.8RXPotpourri.sc: -------------------------------------------------------------------------------- 1 | //RX potpourri (11:30) 2 | //https://class.coursera.org/reactive-002/lecture/149 3 | 4 | //TOC 5 | //– creating observables: 'from' Future , using AsyncSubject 6 | //– computation runs only once, side effects is subtle 7 | //object Observable { 8 | // def apply[T](fut: Future[T]): Observable[T] = { 9 | // val subj = AsyncSubject[T]() 10 | // fut.onComplete { 11 | // case Failure(e) => { subj.onError(e) } 12 | // case Success(t) => { subj.onNext(t); subj.onCompleted() } } 13 | // subj } } 14 | //– Notification data structure for materializing 3 cases/callbacks: onNext,onError,onCompleted 15 | //def materialize: Observable[Notification[T]] = {...} 16 | //– observable.toBlocking.forEach(... // bad practice 17 | //– reduce: alike in SQL – reducing you get table with one row, there you get observable with single value 18 | // only one way to get scalar/iterable: toBlocking // don't do it 19 | //– observable 'from' iterable 20 | //object Observable { 21 | // def apply[T](subscribe: Subscriber[T] => Unit): Observable[T] } 22 | //def from[T](ts: Iterable[T]): Observable[T] = Observable( 23 | // s => { ts.foreach(t => 24 | // { if(s.isUnsubscribed) { break } s.onNext(t) }) 25 | // s.onCompleted() } ) 26 | //– backpressure: https://github.com/ReactiveX/RxJava/wiki/Backpressure 27 | 28 | import rx.lang.scala.Observable 29 | import rx.lang.scala.subjects.AsyncSubject 30 | import scala.util.control.Breaks._ 31 | 32 | // creating Observable 33 | 34 | // Observable 'from' Future 35 | // using AsyncSubject: create channel, push value to channel, once 36 | { 37 | object Observable { 38 | def apply[T](fut: Future[T]): Observable[T] = { 39 | val subj = AsyncSubject[T]() // like promise 40 | 41 | fut.onComplete { // single value, once 42 | case Failure(e) => { subj.onError(e) } 43 | case Success(t) => { subj.onNext(t); subj.onCompleted() } 44 | } 45 | // return subsject 46 | subj 47 | } 48 | } 49 | } 50 | 51 | // from iterable 52 | { 53 | def from[T](ts: Iterable[T]): Observable[T] = 54 | Observable(subs => { 55 | // constructor: object Observable { def apply[T](subs: Subscriber[T] => Unit): Observable[T] = ??? } 56 | ts.foreach(t => { 57 | // check if unsubscribed 58 | if (subs.isUnsubscribed) { break } 59 | subs.onNext(t) // push next value 60 | }) 61 | subs.onCompleted() // close 62 | }) 63 | } 64 | 65 | // observable notifications 66 | // unwrap 3 callback cases (instead of pattern matching) 67 | { // can switch from pattern matching to 3callbacks (and other way around) 68 | def materialize: Observable[Notification[T]] = ??? 69 | 70 | abstract class Notification[+T] // covariant 71 | case class onNext[T](elem: T) extends Notification[T] 72 | case class onError(err: Throwable) extends Notification[Nothing] 73 | case class onCompleted extends Notification[Nothing] 74 | } 75 | 76 | // blocking observable: bad practice 77 | { 78 | val ts: Observable[T] = obs.toBlocking // wait to complete 79 | ts.forEach(t => {???}) 80 | 81 | // check this 82 | val xs: Observable[Long] = Observable.interval(1 second).take(5) 83 | 84 | // bad 85 | val ys: List[Long] = xs.toBlockingObservable.toList 86 | println(ys) 87 | 88 | // ok 89 | val zs: Observable[Long] = xs.sum 90 | val s: Long = zs.toBlockingObservable.single 91 | println(s) 92 | } 93 | 94 | // converting observable to scalar 95 | // reduce: like a SQL: you get not a scalar, but a table with a single row 96 | // you get Observable with a single value 97 | { 98 | def reduce(op: (T,T) => T): Observable[T] 99 | } 100 | 101 | // consumer is slower than producer 102 | // backpressure support: https://github.com/ReactiveX/RxJava/wiki/Backpressure 103 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws4.9ObservableContract.sc: -------------------------------------------------------------------------------- 1 | //Observable Contract (14:19) 2 | //https://class.coursera.org/reactive-002/lecture/151 3 | 4 | //TOC 5 | //– contract that not visible in the type 6 | //– never implement observable yourself, use rx-lib: contract you dont fully know 7 | //– same for all RX types: use lib factories (lib is fragile and overcomlicated) 8 | //– create observable, concept 9 | //object Observable { 10 | // def create(s: Observer => Subscription) = new Observable { 11 | // def subscribe(observer: Observer): Subscription = { 12 | // Implementation(s(observer)) }}} 13 | //val s = Observable.create(f).subscribe(observer) = // conceptually 14 | //val s = Implementation(f(observer)) 15 | //– subscribe/auto-unsubscribe : observer.onCompleted unsubscribe s automagically 16 | //val s = Observable(f).subscribe(observer) = // conceptually 17 | //val s = Implementation(f(Wrap(observer)) 18 | //– auto-unsubscribe behaviour : onCompleted/onError auto unsubscribe sub 19 | //val empty = Observable(sub => { sub.onCompleted() }) 20 | //val s = empty.subscribe(); println(s.isUnsubscribed) 21 | //– call sequence: (onNext)*(onCompleted+onError)? // 0..n 0..1 times 22 | // no calls after onCompleted/onError 23 | // calls always serialized, no overlaps 24 | // https://www.google.com/search?q=rx+design+guidelines 25 | 26 | import jdk.internal.dynalink.linker.LinkerServices.Implementation 27 | import rx.lang.scala.{Observable, Observer, Subscription} 28 | 29 | // Observable conventions 30 | // hidden, not obvious things in contract 31 | 32 | // never implement observable yourself, use rx-lib: contract you dont fully know 33 | // it's not trivial 34 | 35 | // naive approach to observable creation 36 | { 37 | object Observable { 38 | 39 | def create[T](subs: Observer[T] => Subscription): Observable[T] = { 40 | 41 | def subscribe(obs: Observer[T]): Subscription = 42 | Implementation(subs(obs)) // loads of things in implementation 43 | } 44 | } 45 | 46 | val s = Observable.create(func).subscribe(observer) 47 | // conceptually == 48 | val s = Implementation(func(observer)) // loads of things in implementation 49 | } 50 | 51 | // auto-unsubscribe contract 52 | val s = Observable(func).subscribe(observer) 53 | // conceptually == 54 | val s = Implementation(f(wrap(observer))) // loads of things in implementation 55 | // auto-unsubscribe when onCompleted, onError 56 | 57 | // check this 58 | val empty = Observable(subs => { subs.onCompleted() }) // close emmidiately 59 | val s = empty.subscribe() 60 | println(s.isUnsubscribed) 61 | 62 | // Rx contract 63 | // call sequence: (onNext)*(onCompleted+onError)? 64 | // 0..n onNext, 0..1 onCompleted or 0..1 onError 65 | 66 | // never implement observable yourself, use rx-lib: contract you dont fully know 67 | // same for all RX types: use lib factories (lib is fragile and overcomlicated) 68 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws5.1WhyActors.sc: -------------------------------------------------------------------------------- 1 | //Introduction: Why Actors? (14:46) 2 | //https://class.coursera.org/reactive-002/lecture/73 3 | 4 | //TOC 5 | //– Actors can do more than streams : collaborate, communicate 6 | //– Erlang/OTP with actors 7 | //– Akka: actor framework 8 | //– actors motivation: more CPU cores, multi-tasking/multi-threading apps 9 | //– multi-threading: sync problems 10 | //– example: BankAccount, withdraw method, balance read – balance write code 11 | //– what if this code runs in 2 threads 12 | //– one of two updates should'v fail 13 | //– shared state should be protected: balance 14 | //– exlusive access to balance : serialized 15 | //– lock, mutex, semaphore-no-more-than-x-threads 16 | //– in Scala every object has a lock : … this.synchronized { … balance = … } 17 | //– problem: 2 methods (deposit, withdraw) update the same state: balance 18 | //– locks in Scala are reentrant, you can call obj.synchronized many times w/o deadlock 19 | //– example: transfer (from, to) = from.sync { to.sync { from.withdraw ; to.deposit … 20 | //– but deadlock is possible: one thread take a.sync, and another take b.sync – mutually locked 21 | //– can be solved by defining order for taking locks (first for a, then for b) 22 | //– deadlocks, bad CPU utilization, coupling sender and receiver …: we want non-blocking objects/ops 23 | 24 | // reactive streams: data flow in one direction 25 | // actors can do more: collaboration, communication 26 | 27 | // from Erlang implementation Actor model 28 | 29 | // problems that motivates switcing to Actors 30 | 31 | // multicore systems, shared memory: concurrent/parallel execution, 32 | // multi-tasking, multi-threading: parallelization problems -- synchronization 33 | 34 | // example 35 | { 36 | class BankAccount { 37 | private var balance: Int = 0 38 | 39 | def deposit(amount: Int) = 40 | if (amount > 0) balance += amount 41 | 42 | // read-write balance in parallel can violate the invariant and 43 | // lose updates (-50 and -40 for balance = 80), result is undetermined 44 | def withdraw(amount: Int) = 45 | if (9 < amount && amount <= balance) balance -= amount 46 | else sys.error("insufficient funds") 47 | } 48 | } 49 | 50 | // to fix that we need to add sync constructions, serialization 51 | // shared state must be protected 52 | // lock, mutex, semaphore-no-more-than-x-threads 53 | 54 | // in Scala every object has a lock : … this.synchronized { … balance = … } 55 | { 56 | class BankAccount { 57 | private var balance: Int = 0 58 | 59 | def deposit(amount: Int) = this.synchronized { 60 | if (amount > 0) balance += amount 61 | } 62 | 63 | def withdraw(amount: Int) = this.synchronized { 64 | if (9 < amount && amount <= balance) balance -= amount 65 | else sys.error("insufficient funds") 66 | } 67 | } 68 | } 69 | 70 | // and now we facing deadlock problem: 71 | { 72 | class BankAccount { 73 | private var balance: Int = 0 74 | def deposit(amount: Int) = this.synchronized { 75 | if (amount > 0) balance += amount 76 | } 77 | def withdraw(amount: Int) = this.synchronized { 78 | if (9 < amount && amount <= balance) balance -= amount 79 | else sys.error("insufficient funds") 80 | } 81 | 82 | def transfer(from: BankAccount, to: BankAccount, amount: Int) = { 83 | // executing in parallel: a - b and b - a, can lock each other 84 | from.synchronized { 85 | to.synchronized { 86 | from.withdraw(amount) 87 | to.deposit(amount) 88 | } 89 | } 90 | } 91 | } 92 | } 93 | // dead-locks can be avoided using accounts ordering: set locks in the same order 94 | // each time 95 | 96 | // CPU utilization suffers from locks 97 | // communications couples sender and receiver 98 | // complex code 99 | 100 | // Actors: non-blocking objects 101 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws7.2ActorsDistributed2.sc: -------------------------------------------------------------------------------- 1 | //Actors are Distributed Part II (18:17 — optional) 2 | //https://class.coursera.org/reactive-002/lecture/97 3 | 4 | //TOC 5 | //– main node states: joining, up, leaving, exiting, removed, // down 6 | //– cluster needs failure detection 7 | //– every node is monitored (heartbeats) from several others // see: cluster membership state 8 | //– a node unreachable from one other is considered unreachable for all // inconsistent cluster 9 | //– to restore the cluster consensus, node can be removed 10 | //– if we have a lot of nodes, number of communications can be quadratic (1/2N^2), not good … 11 | //– enters 'neighbors' comm. model : ring, 2 or three next nodes clockwise 12 | //– if node become unreachable, it enters to pseudostate 'down' 13 | //– moving node to that state is a policy decision 14 | //– transitions between that states seen as events/messages: MemberUp, MemberRemoved, … 15 | //– DeathWatch: actors on nodes which are removed must be dead // no node, no actors 16 | //– Terminated message: delivery is guaranteed, actor cannot come back, clean-up of remote-deployed childs 17 | //– removed node needs to be completely restarted 18 | //– Terminated message guaranteed, how come? It's a special message, it can be synthesised 19 | //– example: class ClusterWorker extends Actor … { … context.watch(main_receptionist_ref) 20 | //– using cluster is not complicated: subscribe to ClusterEvent.MemberUp, … 21 | //– use context.watch to get Terminated message; stop actor when it's time 22 | 23 | import akka.actor.Actor.Receive 24 | import akka.actor._ 25 | import akka.cluster.{Cluster, ClusterEvent} 26 | 27 | // failure detection, monitoring, watch-Terminate 28 | 29 | // main node states 30 | // joining -> up -> leaving -> exiting -> removed 31 | // up -- MemberUp; removed -- MemberRemoved. 32 | 33 | // worker node have another state (not really a state, a flag more likely): unreachable; 34 | // node can become unreachable at any state. 35 | // After node marked as unreachable, it moved to state 'down', by some policy. 36 | // After reaching consistency, cluster set down node to removed state 37 | // unreachable -> down -> removed 38 | 39 | // node that unreachable must be detected (heartbeat) and removed from cluster 40 | 41 | // neighbors monitoring: array of addressed sorted and considered as circle; 42 | // node 1 monitors 2,3; 2 monitors 3,4; ... 43 | 44 | // if one node detect unreachable neighbour, that information gossiping to 45 | // all other nodes. 46 | // unreachable node will be removed from cluster; until that there is no cluster, 47 | // it's inconsistent 48 | 49 | // example: 50 | /* 51 | main stopped; worker detect main as unreachable; 52 | autodown conf. makes unreachable node go down; 53 | worker become leader; leader move main node to removed; 54 | that cause shutdown the program. 55 | */ 56 | 57 | // Cluster and DeathWatch 58 | 59 | // actors on removed nodes must be dead (killed properly) to achieve cluster consistency. 60 | // or, on live nodes, children actors of dead parent must be stopped. 61 | // It's possible to handle that requirement, using Terminated message (watch). 62 | // delivery of Terminated is guaranteed (it can be synthesized). 63 | 64 | // terminated is the last message from actor. 65 | // That means, node can't come back. It must be restarted. 66 | 67 | // apply that to ClusterWorker 68 | // find receptionist ActorRef, watch it, stop(self) if receptionist Terminated 69 | { 70 | class ClusterWorker extends Actor with ActorLogging { 71 | val cluster = Cluster(context.system) 72 | cluster.subscribe(self, classOf[ClusterEvent.MemberUp]) 73 | // get main node address 74 | val main = cluster.selfAddress.copy(port = Some(2552)) 75 | // join to main cluster 76 | cluster.join(main) 77 | 78 | override def receive: Receive = { 79 | case ClusterEvent.MemberUp(member) => 80 | if (member.address == main) 81 | context.actorSelection(RootActorPath(main) / "user" / "app" / "receptionist") ! Identify("42") 82 | case ActorIdentity("42", None) => context.stop(self) 83 | case ActorIdentity("42", Some(ref)) => 84 | log.info("receptionist is at {}", ref) 85 | context.watch(ref) 86 | case Terminated(_) => context.stop(self) 87 | } 88 | 89 | override def postStop(): Unit = AsyncWebClient.shutdown() 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /worksheets/src/main/scala/ws7.5ScalabilityByActors.sc: -------------------------------------------------------------------------------- 1 | //Scalability (17:00) 2 | //https://class.coursera.org/reactive-002/lecture/103 3 | 4 | //TOC 5 | //– favor scalability over single-threaded performance 6 | //– low performance: system is slow for a single client 7 | //– low scalability: system is fast for 1 client, but slow for many 8 | //– req/sec limit as high as possible / resp.time is constant 9 | //– it means (for Actor System): many stateless actors (replicas) running concurrently 10 | //– one actor = one message at a time 11 | //– async message passing gives as a possibility for scaling up 12 | //– worker pool, message routing: stateful (round robin, smallest queue, …); 13 | // stateless(random, consistent hashing, …) 14 | //– stateless router: can be more than one, no need to share state 15 | //– round-robin routing: imbalances lead to larger spread in latency spectrum 16 | //– smallest mailbox routing: priority, routees need to be local for inspection of queue; hight routing cost 17 | //– shared work queue: required routees to be local; most homogenous latency; effectively a pull model. 18 | //– adaptive routing: requires feedback about proc.times, latencies, queue sizes 19 | // feedback control theory // oscillations, over-dampling 20 | //– random routing: can stochastically lead to imbalance ; stateless 21 | //– consistent hashing: can exhibit systematic imbalances based on hashing function 22 | // sticky sessions is possible 23 | //– consistent hashing can be used if substreams correspond to independent parts of the state 24 | // replication of stateful actors // actors need to communicate 25 | // sharding and stuff? 26 | //– multiple writers to the same state require appropriate data structures and are eventually consistent 27 | // distributed store example 28 | //– persistent stateful actors can be replicated 29 | //– not only scalability, fault tolerance as well 30 | // persistent state, recover failed actor (on othe node maybe) 31 | // can start many instances, only one is active 32 | // consistent routing to the active instance // session 33 | // message buffering 34 | //– vertical scalability by async messages 35 | //– horizontal scalability by location transparency 36 | 37 | // features of actors 38 | // scale up: async messages; 39 | // scale out: more actors (stateless), location transparency. 40 | 41 | // we favor scalability over performance (for single client) 42 | // req/sec should grow linearly with number of clients; constant resp.time 43 | 44 | // it means: stateless concurrent actor replicas processing messages, 45 | // one actor -- one message at a time. 46 | // more clients -- more actors. 47 | 48 | // Routing messages to worker pools: 49 | // -- stateful: round robin, smallest queue, adaptive, ... 50 | // -- stateless: random, consistent hashing, ... 51 | 52 | // stateless is less accurate but more scalable (don't have to share anything) 53 | 54 | // stateful 55 | 56 | // round-robin: equal distribution of messages, does not give a fuck about node workload, 57 | // node mailbox may be full 58 | 59 | // smallest mailbox routing: evens out imbalances; high routing cost 60 | 61 | // shared work queue: requires routees to be local; effectively a pull model; 62 | // most homogenous latency 63 | 64 | // adaptive routing, good choice: requires feedback from workers; steering the routing weights 65 | // (feedback control theory: oscillations, over-dampening) 66 | 67 | // stateless 68 | 69 | // random routing: equal (almost) distribution of messages, low routing overhead, 70 | // may have several distributed routers; 71 | // nodes workload are ignored. 72 | 73 | // consistent hashing: 'all red to 1, all blue to 2'; 74 | // nodes workload are ignored; hashing function must be good; 75 | // substreams bundling, sticky sessions, CDN functionality 76 | 77 | // sticky session means: we can use stateful actors, user will see updated state 78 | // working with the same actor on every request. 79 | // It's possible if substreams (of requests) correspond to independent parts of the state. 80 | 81 | // consistent hashing routing can be used to replicate stateful actors: 82 | // input stream splits in such a fashion that a state affected by substreams are independent. 83 | // Appropriate data structure required: CRDT: Convergent and Commutative Replicated Data Types 84 | // for eventual consistency. 85 | // In such a case replication used more for fault tolerance than scalability. 86 | 87 | // replication of persisted stateful actors 88 | 89 | // based on persisted state (persist-persisted) 90 | // only one instance is active 91 | // consistent routing to the active instance 92 | // buffering messages during recovery 93 | // migration: recovery at a different location 94 | --------------------------------------------------------------------------------