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