├── .gitignore ├── README.md ├── actorbintree ├── build.sbt ├── project │ ├── ReactiveBuild.scala │ ├── build.properties │ └── plugins.sbt └── src │ ├── main │ └── scala │ │ ├── actorbintree │ │ └── BinaryTreeSet.scala │ │ └── common │ │ └── package.scala │ └── test │ └── scala │ └── actorbintree │ └── BinaryTreeSuite.scala ├── 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 ├── example ├── build.sbt ├── project │ ├── ReactiveBuild.scala │ ├── build.properties │ └── plugins.sbt └── src │ ├── main │ └── scala │ │ ├── common │ │ └── package.scala │ │ └── example │ │ └── Lists.scala │ └── test │ └── scala │ └── example │ └── ListsSuite.scala ├── 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 ├── 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 └── suggestions ├── build.sbt ├── project ├── ReactiveBuild.scala ├── build.properties └── plugins.sbt └── src ├── main ├── 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 /.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 | 19 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Principles of Reactive Programming 2 | Solutions to the assignments from the Coursera course Principles of Reactive Programming by EPFL, Spring 2015 session. 3 | 4 | Those code passes all the tests on the Coursera site, but they are not necessarily 100% correct. 5 | 6 | The code may only be used for referencing. 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /actorbintree/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | import scala.util.Random 9 | 10 | object BinaryTreeSet { 11 | 12 | trait Operation { 13 | def requester: ActorRef 14 | 15 | def id: Int 16 | 17 | def elem: Int 18 | } 19 | 20 | trait OperationReply { 21 | def id: Int 22 | } 23 | 24 | /** Request with identifier `id` to insert an element `elem` into the tree. 25 | * The actor at reference `requester` should be notified when this operation 26 | * is completed. 27 | */ 28 | case class Insert(requester: ActorRef, id: Int, elem: Int) extends Operation 29 | 30 | /** Request with identifier `id` to check whether an element `elem` is present 31 | * in the tree. The actor at reference `requester` should be notified when 32 | * this operation is completed. 33 | */ 34 | case class Contains(requester: ActorRef, id: Int, elem: Int) extends Operation 35 | 36 | /** Request with identifier `id` to remove the element `elem` from the tree. 37 | * The actor at reference `requester` should be notified when this operation 38 | * is completed. 39 | */ 40 | case class Remove(requester: ActorRef, id: Int, elem: Int) extends Operation 41 | 42 | /** Request to perform garbage collection */ 43 | case object GC 44 | 45 | /** Holds the answer to the Contains request with identifier `id`. 46 | * `result` is true if and only if the element is present in the tree. 47 | */ 48 | case class ContainsResult(id: Int, result: Boolean) extends OperationReply 49 | 50 | /** Message to signal successful completion of an insert or remove operation. */ 51 | case class OperationFinished(id: Int) extends OperationReply 52 | 53 | } 54 | 55 | 56 | class BinaryTreeSet extends Actor { 57 | 58 | import BinaryTreeSet._ 59 | import BinaryTreeNode._ 60 | 61 | def createRoot: ActorRef = context.actorOf(props(0, initiallyRemoved = true)) 62 | 63 | var root = createRoot 64 | 65 | // optional 66 | var pendingQueue = Queue.empty[Operation] 67 | 68 | // optional 69 | def receive = normal 70 | 71 | // optional 72 | /** Accepts `Operation` and `GC` messages. */ 73 | val normal: Receive = { 74 | case o: Operation => root ! o 75 | 76 | case GC => 77 | val newRoot = createRoot 78 | root ! CopyTo(newRoot) 79 | context become garbageCollecting(newRoot) 80 | } 81 | 82 | // optional 83 | /** Handles messages while garbage collection is performed. 84 | * `newRoot` is the root of the new binary tree where we want to copy 85 | * all non-removed elements into. 86 | */ 87 | def garbageCollecting(newRoot: ActorRef): Receive = { 88 | case GC => 89 | 90 | case o: Operation => pendingQueue :+= o 91 | 92 | case CopyFinished => 93 | root ! PoisonPill 94 | root = newRoot 95 | pendingQueue foreach (root ! _) 96 | pendingQueue = Queue.empty[Operation] 97 | context become normal 98 | } 99 | } 100 | 101 | object BinaryTreeNode { 102 | 103 | trait Position 104 | 105 | case object Left extends Position 106 | 107 | case object Right extends Position 108 | 109 | case class CopyTo(treeNode: ActorRef) 110 | 111 | case object CopyFinished 112 | 113 | def props(elem: Int, initiallyRemoved: Boolean) = Props(classOf[BinaryTreeNode], elem, initiallyRemoved) 114 | } 115 | 116 | class BinaryTreeNode(val elem: Int, initiallyRemoved: Boolean) extends Actor { 117 | 118 | import BinaryTreeNode._ 119 | import BinaryTreeSet._ 120 | 121 | var subtrees = Map[Position, ActorRef]() 122 | var removed = initiallyRemoved 123 | 124 | // optional 125 | def receive = normal 126 | 127 | // optional 128 | /** Handles `Operation` messages and `CopyTo` requests. */ 129 | val normal: Receive = { 130 | case i: Insert => 131 | if (i.elem > elem) { 132 | if (subtrees isDefinedAt Right) subtrees(Right) ! i 133 | else { 134 | subtrees += Right -> context.actorOf(props(i.elem, initiallyRemoved = false)) 135 | i.requester ! OperationFinished(i.id) 136 | } 137 | } else if (i.elem < elem) { 138 | if (subtrees isDefinedAt Left) subtrees(Left) ! i 139 | else { 140 | subtrees += Left -> context.actorOf(props(i.elem, initiallyRemoved = false)) 141 | i.requester ! OperationFinished(i.id) 142 | } 143 | } else { 144 | removed = false 145 | i.requester ! OperationFinished(i.id) 146 | } 147 | 148 | case c: Contains => 149 | if (c.elem > elem) { 150 | if (subtrees isDefinedAt Right) subtrees(Right) ! c 151 | else c.requester ! ContainsResult(c.id, result = false) 152 | } else if (c.elem < elem) { 153 | if (subtrees isDefinedAt Left) subtrees(Left) ! c 154 | else c.requester ! ContainsResult(c.id, result = false) 155 | } else 156 | c.requester ! ContainsResult(c.id, result = !removed) 157 | 158 | case r: Remove => 159 | if (r.elem > elem) { 160 | if (subtrees isDefinedAt Right) subtrees(Right) ! r 161 | else r.requester ! OperationFinished(r.id) 162 | } else if (r.elem < elem) { 163 | if (subtrees isDefinedAt Left) subtrees(Left) ! r 164 | else r.requester ! OperationFinished(r.id) 165 | } else { 166 | removed = true 167 | r.requester ! OperationFinished(r.id) 168 | } 169 | 170 | case CopyTo(newRoot) => 171 | if (subtrees.isEmpty && removed) { 172 | context.parent ! CopyFinished 173 | self ! PoisonPill 174 | } else { 175 | val expected = subtrees.map(_._2).toSet 176 | expected foreach (_ ! CopyTo(newRoot)) 177 | context become copying(expected, insertConfirmed = removed) 178 | if (!removed) newRoot ! Insert(self, Random.nextInt(), elem) 179 | } 180 | } 181 | 182 | // optional 183 | /** `expected` is the set of ActorRefs whose replies we are waiting for, 184 | * `insertConfirmed` tracks whether the copy of this node to the new tree has been confirmed. 185 | */ 186 | def copying(expected: Set[ActorRef], insertConfirmed: Boolean): Receive = { 187 | case OperationFinished(id) => 188 | if (expected.isEmpty) context.parent ! CopyFinished 189 | else context become copying(expected, insertConfirmed = true) 190 | 191 | case CopyFinished => 192 | val remaining = expected - sender 193 | if (remaining.isEmpty && insertConfirmed) context.parent ! CopyFinished 194 | else context become copying(remaining, insertConfirmed) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /actorbintree/src/test/scala/actorbintree/BinaryTreeSuite.scala: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2009-2015 Typesafe Inc. 3 | */ 4 | package actorbintree 5 | 6 | import akka.actor.{ Props, ActorRef, ActorSystem } 7 | import org.scalatest.{ BeforeAndAfterAll, FlatSpec } 8 | import akka.testkit.{ TestProbe, ImplicitSender, TestKit } 9 | import org.scalatest.Matchers 10 | import scala.util.Random 11 | import scala.concurrent.duration._ 12 | import org.scalatest.FunSuiteLike 13 | 14 | class BinaryTreeSuite(_system: ActorSystem) extends TestKit(_system) with FunSuiteLike with Matchers with BeforeAndAfterAll with ImplicitSender 15 | { 16 | 17 | def this() = this(ActorSystem("BinaryTreeSuite")) 18 | 19 | override def afterAll: Unit = system.shutdown() 20 | 21 | import actorbintree.BinaryTreeSet._ 22 | 23 | def receiveN(requester: TestProbe, ops: Seq[Operation], expectedReplies: Seq[OperationReply]): Unit = 24 | requester.within(5.seconds) { 25 | val repliesUnsorted = for (i <- 1 to ops.size) yield try { 26 | requester.expectMsgType[OperationReply] 27 | } catch { 28 | case ex: Throwable if ops.size > 10 => fail(s"failure to receive confirmation $i/${ops.size}", ex) 29 | case ex: Throwable => fail(s"failure to receive confirmation $i/${ops.size}\nRequests:" + ops.mkString("\n ", "\n ", ""), ex) 30 | } 31 | val replies = repliesUnsorted.sortBy(_.id) 32 | if (replies != expectedReplies) { 33 | val pairs = (replies zip expectedReplies).zipWithIndex filter (x => x._1._1 != x._1._2) 34 | fail("unexpected replies:" + pairs.map(x => s"at index ${x._2}: got ${x._1._1}, expected ${x._1._2}").mkString("\n ", "\n ", "")) 35 | } 36 | } 37 | 38 | def verify(probe: TestProbe, ops: Seq[Operation], expected: Seq[OperationReply]): Unit = { 39 | val topNode = system.actorOf(Props[BinaryTreeSet]) 40 | 41 | ops foreach { op => 42 | topNode ! op 43 | } 44 | 45 | receiveN(probe, ops, expected) 46 | // the grader also verifies that enough actors are created 47 | } 48 | 49 | test("proper inserts and lookups") { 50 | val topNode = system.actorOf(Props[BinaryTreeSet]) 51 | 52 | topNode ! Contains(testActor, id = 1, 1) 53 | expectMsg(ContainsResult(1, false)) 54 | 55 | topNode ! Insert(testActor, id = 2, 1) 56 | topNode ! Contains(testActor, id = 3, 1) 57 | 58 | expectMsg(OperationFinished(2)) 59 | expectMsg(ContainsResult(3, true)) 60 | } 61 | 62 | test("instruction example") { 63 | val requester = TestProbe() 64 | val requesterRef = requester.ref 65 | val ops = List( 66 | Insert(requesterRef, id=100, 1), 67 | Contains(requesterRef, id=50, 2), 68 | Remove(requesterRef, id=10, 1), 69 | Insert(requesterRef, id=20, 2), 70 | Contains(requesterRef, id=80, 1), 71 | Contains(requesterRef, id=70, 2) 72 | ) 73 | 74 | val expectedReplies = List( 75 | OperationFinished(id=10), 76 | OperationFinished(id=20), 77 | ContainsResult(id=50, false), 78 | ContainsResult(id=70, true), 79 | ContainsResult(id=80, false), 80 | OperationFinished(id=100) 81 | ) 82 | 83 | verify(requester, ops, expectedReplies) 84 | } 85 | 86 | test("behave identically to built-in set (includes GC)") { 87 | val rnd = new Random() 88 | def randomOperations(requester: ActorRef, count: Int): Seq[Operation] = { 89 | def randomElement: Int = rnd.nextInt(100) 90 | def randomOperation(requester: ActorRef, id: Int): Operation = rnd.nextInt(4) match { 91 | case 0 => Insert(requester, id, randomElement) 92 | case 1 => Insert(requester, id, randomElement) 93 | case 2 => Contains(requester, id, randomElement) 94 | case 3 => Remove(requester, id, randomElement) 95 | } 96 | 97 | for (seq <- 0 until count) yield randomOperation(requester, seq) 98 | } 99 | 100 | def referenceReplies(operations: Seq[Operation]): Seq[OperationReply] = { 101 | var referenceSet = Set.empty[Int] 102 | def replyFor(op: Operation): OperationReply = op match { 103 | case Insert(_, seq, elem) => 104 | referenceSet = referenceSet + elem 105 | OperationFinished(seq) 106 | case Remove(_, seq, elem) => 107 | referenceSet = referenceSet - elem 108 | OperationFinished(seq) 109 | case Contains(_, seq, elem) => 110 | ContainsResult(seq, referenceSet(elem)) 111 | } 112 | 113 | for (op <- operations) yield replyFor(op) 114 | } 115 | 116 | val requester = TestProbe() 117 | val topNode = system.actorOf(Props[BinaryTreeSet]) 118 | val count = 1000 119 | 120 | val ops = randomOperations(requester.ref, count) 121 | val expectedReplies = referenceReplies(ops) 122 | 123 | ops foreach { op => 124 | topNode ! op 125 | if (rnd.nextDouble() < 0.1) topNode ! GC 126 | } 127 | receiveN(requester, ops, expectedReplies) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /calculator/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /calculator/src/main/scala/calculator/Calculator.scala: -------------------------------------------------------------------------------- 1 | package calculator 2 | 3 | sealed abstract class Expr 4 | 5 | final case class Literal(v: Double) extends Expr 6 | 7 | final case class Ref(name: String) extends Expr 8 | 9 | final case class Plus(a: Expr, b: Expr) extends Expr 10 | 11 | final case class Minus(a: Expr, b: Expr) extends Expr 12 | 13 | final case class Times(a: Expr, b: Expr) extends Expr 14 | 15 | final case class Divide(a: Expr, b: Expr) extends Expr 16 | 17 | object Calculator { 18 | def computeValues(namedExpressions: Map[String, Signal[Expr]]): Map[String, Signal[Double]] = { 19 | var result = Map[String, Signal[Double]]() 20 | 21 | namedExpressions.foreach {case(k, v) => result += k -> Signal(eval(v(), namedExpressions - k))} 22 | 23 | result 24 | } 25 | 26 | def eval(expr: Expr, references: Map[String, Signal[Expr]]): Double = expr match { 27 | case Literal(v) => v 28 | case Ref(name) => eval(getReferenceExpr(name, references), references) 29 | case Plus(a, b) => eval(a, references) + eval(b, references) 30 | case Minus(a, b) => eval(a, references) - eval(b, references) 31 | case Times(a, b) => eval(a, references) * eval(b, references) 32 | case Divide(a, b) => eval(a, references) / eval(b, references) 33 | } 34 | 35 | /** Get the Expr for a referenced variables. 36 | * If the variable is not known, returns a literal NaN. 37 | */ 38 | private def getReferenceExpr(name: String, references: Map[String, Signal[Expr]]) = { 39 | references.get(name).fold[Expr] { 40 | Literal(Double.NaN) 41 | } { exprSignal => 42 | exprSignal() 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /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 | Signal(b() * b() - 4 * a() * c()) 7 | } 8 | 9 | def computeSolutions(a: Signal[Double], b: Signal[Double], 10 | c: Signal[Double], delta: Signal[Double]): Signal[Set[Double]] = { 11 | Signal { 12 | var result = Set[Double]() 13 | 14 | if (computeDelta(a, b, c)() > 0) { 15 | result += (-b() + math.sqrt(computeDelta(a, b, c)())) / (2 * a()) 16 | result += (-b() - math.sqrt(computeDelta(a, b, c)())) / (2 * a()) 17 | } else if (computeDelta(a, b, c)() == 0) { 18 | result += (-b()) / (2 * a()) 19 | } 20 | 21 | result 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | Signal(MaxTweetLength - tweetLength(tweetText())) 8 | } 9 | 10 | def colorForRemainingCharsCount(remainingCharsCount: Signal[Int]): Signal[String] = { 11 | Signal { 12 | if (remainingCharsCount() >= 15) { 13 | "green" 14 | } else if (0 <= remainingCharsCount() && remainingCharsCount() <= 14) { 15 | "orange" 16 | } else { 17 | "red" 18 | } 19 | } 20 | } 21 | 22 | /** Computes the length of a tweet, given its text string. 23 | * This is not equivalent to text.length, as tweet lengths count the number 24 | * of Unicode *code points* in the string. 25 | * Note that this is still a simplified view of the reality. Full details 26 | * can be found at 27 | * https://dev.twitter.com/overview/api/counting-characters 28 | */ 29 | private def tweetLength(text: String): Int = { 30 | /* This should be simply text.codePointCount(0, text.length), but it 31 | * is not implemented in Scala.js 0.6.2. 32 | */ 33 | if (text.isEmpty) 0 34 | else { 35 | text.length - text.init.zip(text.tail).count( 36 | (Character.isSurrogatePair _).tupled) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /calculator/web-ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Functional Reactive Calculator 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 |

Functional Reactive Calculator

17 | 18 | 19 |

Tweet measurer

20 | 21 |
22 |
23 | 24 | 25 | Characters remaining: 26 |
27 |
28 | 29 | 30 |

2nd order polynomial solver

31 | 32 |
33 |
34 | x² 35 |
36 | + 37 |
38 | x 39 |
40 | + 41 |
42 | 43 |
44 |
45 | 46 |

47 | Δ = b² - 4ac = ?
48 | Solution(s): ? 49 |

50 | 51 | 52 |

Calculator

53 | 54 |

Once properly implemented, this calculator can compute values of variables 55 | depending on other variables.

56 | 57 |

Only very simple expressions are valid, here. It can be one of:

58 | 59 |
    60 |
  • A number
  • 61 |
  • The name of a variable (from 'a' to 'j')
  • 62 |
  • An expression of the form '<expr> <op> <expr>' where 63 | <expr> is a number or a variable name, and <op> is one of + - * /
    64 | Note that spaces are important, and that there cannot be more than one operation per cell.
  • 65 |
66 | 67 |

Any error, such as a malformed expression, or cyclic references between 68 | variables, will result in NaN (Not-a-Number).

69 | 70 |
71 |
72 | 73 |
74 | 75 |
76 |
77 | = ? 78 |
79 |
80 |
81 | 82 |
83 | 84 |
85 |
86 | = ? 87 |
88 |
89 |
90 | 91 |
92 | 93 |
94 |
95 | = ? 96 |
97 |
98 |
99 | 100 |
101 | 102 |
103 |
104 | = ? 105 |
106 |
107 |
108 | 109 |
110 | 111 |
112 |
113 | = ? 114 |
115 |
116 |
117 | 118 |
119 | 120 |
121 |
122 | = ? 123 |
124 |
125 |
126 | 127 |
128 | 129 |
130 |
131 | = ? 132 |
133 |
134 |
135 | 136 |
137 | 138 |
139 |
140 | = ? 141 |
142 |
143 |
144 | 145 |
146 | 147 |
148 |
149 | = ? 150 |
151 |
152 |
153 | 154 |
155 | 156 |
157 |
158 | = ? 159 |
160 |
161 |
162 | 163 |
164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /calculator/web-ui/src/main/scala/calculator/CalculatorUI.scala: -------------------------------------------------------------------------------- 1 | package calculator 2 | 3 | import scala.scalajs.js 4 | import org.scalajs.dom 5 | import org.scalajs.dom.html 6 | import dom.document 7 | 8 | object CalculatorUI extends js.JSApp { 9 | def main(): Unit = { 10 | try { 11 | setupTweetMeasurer() 12 | setup2ndOrderPolynomial() 13 | setupCalculator() 14 | } catch { 15 | case th: Throwable => 16 | th.printStackTrace() 17 | } 18 | } 19 | 20 | // Helpers 21 | 22 | def elementById[A <: js.Any](id: String): A = 23 | document.getElementById(id).asInstanceOf[A] 24 | 25 | def elementValueSignal(element: html.Element, 26 | getValue: () => String): Signal[String] = { 27 | var prevVal = getValue() 28 | val value = new Var(prevVal) 29 | val onChange = { (event: dom.Event) => 30 | // Reconstruct manually the optimization at the root of the graph 31 | val newVal = getValue() 32 | if (newVal != prevVal) { 33 | prevVal = newVal 34 | value() = newVal 35 | } 36 | } 37 | element.addEventListener("change", onChange) 38 | element.addEventListener("keypress", onChange) 39 | element.addEventListener("keyup", onChange) 40 | value 41 | } 42 | 43 | def inputValueSignal(input: html.Input): Signal[String] = 44 | elementValueSignal(input, () => input.value) 45 | 46 | def textAreaValueSignal(textAreaID: String): Signal[String] = { 47 | val textArea = elementById[html.TextArea](textAreaID) 48 | elementValueSignal(textArea, () => textArea.value) 49 | } 50 | 51 | private lazy val ClearCssClassRegExp = 52 | new js.RegExp(raw"""(?:^|\s)has-error(?!\S)""", "g") 53 | 54 | def doubleValueOfInput(input: html.Input): Signal[Double] = { 55 | val text = inputValueSignal(input) 56 | val parent = input.parentElement 57 | Signal { 58 | import js.JSStringOps._ 59 | parent.className = parent.className.jsReplace(ClearCssClassRegExp, "") 60 | try { 61 | text().toDouble 62 | } catch { 63 | case e: NumberFormatException => 64 | parent.className += " has-error" 65 | Double.NaN 66 | } 67 | } 68 | } 69 | 70 | // TWEET LENGTH 71 | 72 | def setupTweetMeasurer(): Unit = { 73 | val tweetText = textAreaValueSignal("tweettext") 74 | val remainingCharsArea = 75 | document.getElementById("tweetremainingchars").asInstanceOf[html.Span] 76 | 77 | val remainingCount = TweetLength.tweetRemainingCharsCount(tweetText) 78 | Signal { 79 | remainingCharsArea.textContent = remainingCount().toString 80 | } 81 | 82 | val color = TweetLength.colorForRemainingCharsCount(remainingCount) 83 | Signal { 84 | remainingCharsArea.style.color = color() 85 | } 86 | } 87 | 88 | // 2ND ORDER POLYNOMIAL 89 | 90 | def setup2ndOrderPolynomial(): Unit = { 91 | val ids = List("polyroota", "polyrootb", "polyrootc") 92 | val inputs = ids.map(id => elementById[html.Input](id)) 93 | val doubleValues = inputs.map(doubleValueOfInput) 94 | val List(a, b, c) = doubleValues 95 | 96 | val delta = Polynomial.computeDelta(a, b, c) 97 | val deltaArea = elementById[html.Span]("polyrootdelta") 98 | Signal { 99 | deltaArea.textContent = delta().toString 100 | } 101 | 102 | val solutions = Polynomial.computeSolutions(a, b, c, delta) 103 | val solutionsArea = elementById[html.Span]("polyrootsolutions") 104 | Signal { 105 | solutionsArea.textContent = solutions().toString 106 | } 107 | } 108 | 109 | // CALCULATOR 110 | 111 | def setupCalculator(): Unit = { 112 | val names = (0 until 10).map(i => ('a' + i).toChar.toString) 113 | 114 | val inputs = names.map(name => elementById[html.Input]("calculatorexpr" + name)) 115 | val exprs = inputs.map(exprOfInput) 116 | 117 | val namedExpressions = names.zip(exprs).toMap 118 | 119 | val namedValues = Calculator.computeValues(namedExpressions) 120 | 121 | assert(namedValues.keySet == namedExpressions.keySet) 122 | 123 | for ((name, valueSignal) <- namedValues) { 124 | val span = elementById[html.Span]("calculatorval" + name) 125 | var dehighlightTimeout: Option[js.timers.SetTimeoutHandle] = None 126 | Signal { 127 | span.textContent = valueSignal().toString 128 | 129 | span.style.backgroundColor = "#ffff99" 130 | 131 | dehighlightTimeout.foreach(js.timers.clearTimeout) 132 | dehighlightTimeout = Some(js.timers.setTimeout(1500) { 133 | dehighlightTimeout = None 134 | span.style.backgroundColor = "white" 135 | }) 136 | } 137 | } 138 | } 139 | 140 | def exprOfInput(input: html.Input): Signal[Expr] = { 141 | val text = inputValueSignal(input) 142 | val parent = input.parentElement 143 | Signal { 144 | import js.JSStringOps._ 145 | parent.className = parent.className.jsReplace(ClearCssClassRegExp, "") 146 | try { 147 | parseExpr(text()) 148 | } catch { 149 | case e: IllegalArgumentException => 150 | parent.className += " has-error" 151 | Literal(Double.NaN) 152 | } 153 | } 154 | } 155 | 156 | def parseExpr(text: String): Expr = { 157 | def parseSimple(text: String): Expr = { 158 | if (text.forall(l => l >= 'a' && l <= 'z')) { 159 | Ref(text) 160 | } else { 161 | try { 162 | Literal(text.toDouble) 163 | } catch { 164 | case e: NumberFormatException => 165 | throw new IllegalArgumentException(s"$text is neither a variable name nor a number") 166 | } 167 | } 168 | } 169 | 170 | text.split(" ").map(_.trim).filter(_ != "") match { 171 | case Array(x) => parseSimple(x) 172 | case Array(aText, op, bText) => 173 | val a = parseSimple(aText) 174 | val b = parseSimple(bText) 175 | op match { 176 | case "+" => Plus(a, b) 177 | case "-" => Minus(a, b) 178 | case "*" => Times(a, b) 179 | case "/" => Divide(a, b) 180 | case _ => 181 | throw new IllegalArgumentException(s"$op is not a valid operator") 182 | } 183 | case _ => 184 | throw new IllegalArgumentException(s"$text is not a valid simple expression") 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /example/build.sbt: -------------------------------------------------------------------------------- 1 | submitProjectName := "example" 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 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /example/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("ch.epfl.lamp" % "sbt-coursera" % "0.5") 2 | -------------------------------------------------------------------------------- /example/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 | -------------------------------------------------------------------------------- /example/src/main/scala/example/Lists.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import common._ 4 | 5 | object Lists { 6 | /** 7 | * This method computes the sum of all elements in the list xs. There are 8 | * multiple techniques that can be used for implementing this method, and 9 | * you will learn during the class. 10 | * 11 | * For this example assignment you can use the following methods in class 12 | * `List`: 13 | * 14 | * - `xs.isEmpty: Boolean` returns `true` if the list `xs` is empty 15 | * - `xs.head: Int` returns the head element of the list `xs`. If the list 16 | * is empty an exception is thrown 17 | * - `xs.tail: List[Int]` returns the tail of the list `xs`, i.e. the the 18 | * list `xs` without its `head` element 19 | * 20 | * ''Hint:'' instead of writing a `for` or `while` loop, think of a recursive 21 | * solution. 22 | * 23 | * @param xs A list of natural numbers 24 | * @return The sum of all elements in `xs` 25 | */ 26 | def sum(xs: List[Int]): Int = { 27 | if (xs.isEmpty) 0 28 | else if (xs.tail.isEmpty) xs.head 29 | else xs.head + sum(xs.tail) 30 | } 31 | 32 | 33 | /** 34 | * This method returns the largest element in a list of integers. If the 35 | * list `xs` is empty it throws a `java.util.NoSuchElementException`. 36 | * 37 | * You can use the same methods of the class `List` as mentioned above. 38 | * 39 | * ''Hint:'' Again, think of a recursive solution instead of using looping 40 | * constructs. You might need to define an auxiliary method. 41 | * 42 | * @param xs A list of natural numbers 43 | * @return The largest element in `xs` 44 | * @throws java.util.NoSuchElementException if `xs` is an empty list 45 | */ 46 | def max(xs: List[Int]): Int = { 47 | if (xs.isEmpty) throw new java.util.NoSuchElementException 48 | else if (xs.tail.isEmpty) xs.head 49 | else math.max(xs.head, max(xs.tail)) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /example/src/test/scala/example/ListsSuite.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import org.scalatest.FunSuite 4 | 5 | import org.junit.runner.RunWith 6 | import org.scalatest.junit.JUnitRunner 7 | 8 | /** 9 | * This class implements a ScalaTest test suite for the methods in object 10 | * `Lists` that need to be implemented as part of this assignment. A test 11 | * suite is simply a collection of individual tests for some specific 12 | * component of a program. 13 | * 14 | * A test suite is created by defining a class which extends the type 15 | * `org.scalatest.FunSuite`. When running ScalaTest, it will automatically 16 | * find this class and execute all of its tests. 17 | * 18 | * Adding the `@RunWith` annotation enables the test suite to be executed 19 | * inside eclipse using the built-in JUnit test runner. 20 | * 21 | * You have two options for running this test suite: 22 | * 23 | * - Start the sbt console and run the "test" command 24 | * - Right-click this file in eclipse and chose "Run As" - "JUnit Test" 25 | */ 26 | @RunWith(classOf[JUnitRunner]) 27 | class ListsSuite extends FunSuite { 28 | /** 29 | * Tests are written using the `test` operator which takes two arguments: 30 | * 31 | * - A description of the test. This description has to be unique, no two 32 | * tests can have the same description. 33 | * - The test body, a piece of Scala code that implements the test 34 | * 35 | * The most common way to implement a test body is using the method `assert` 36 | * which tests that its argument evaluates to `true`. So one of the simplest 37 | * successful tests is the following: 38 | */ 39 | test("one plus one is two")(assert(1 + 1 == 2)) 40 | 41 | /** 42 | * In Scala, it is allowed to pass an argument to a method using the block 43 | * syntax, i.e. `{ argument }` instead of parentheses `(argument)`. 44 | * 45 | * This allows tests to be written in a more readable manner: 46 | */ 47 | test("one plus one is three?") { 48 | assert(1 + 1 != 3) // This assertion fails! Go ahead and fix it. 49 | } 50 | 51 | /** 52 | * One problem with the previous (failing) test is that ScalaTest will 53 | * only tell you that a test failed, but it will not tell you what was 54 | * the reason for the failure. The output looks like this: 55 | * 56 | * {{{ 57 | * [info] - one plus one is three? *** FAILED *** 58 | * }}} 59 | * 60 | * This situation can be improved by using a special equality operator 61 | * `===` instead of `==` (this is only possible in ScalaTest). So if you 62 | * run the next test, ScalaTest will show the following output: 63 | * 64 | * {{{ 65 | * [info] - details why one plus one is not three *** FAILED *** 66 | * [info] 2 did not equal 3 (ListsSuite.scala:67) 67 | * }}} 68 | * 69 | * We recommend to always use the `===` equality operator when writing tests. 70 | */ 71 | test("details why one plus one is not three") { 72 | assert(1 + 1 !== 3) // Fix me, please! 73 | } 74 | 75 | /** 76 | * In order to test the exceptional behavior of a methods, ScalaTest offers 77 | * the `intercept` operation. 78 | * 79 | * In the following example, we test the fact that the method `intNotZero` 80 | * throws an `IllegalArgumentException` if its argument is `0`. 81 | */ 82 | test("intNotZero throws an exception if its argument is 0") { 83 | intercept[IllegalArgumentException] { 84 | intNotZero(0) 85 | } 86 | } 87 | 88 | def intNotZero(x: Int): Int = { 89 | if (x == 0) throw new IllegalArgumentException("zero is not allowed") 90 | else x 91 | } 92 | 93 | 94 | /** 95 | * Now we finally write some tests for the list functions that have to be 96 | * implemented for this assignment. We fist import all members of the 97 | * `List` object. 98 | */ 99 | import Lists._ 100 | 101 | 102 | /** 103 | * We only provide two very basic tests for you. Write more tests to make 104 | * sure your `sum` and `max` methods work as expected. 105 | * 106 | * In particular, write tests for corner cases: negative numbers, zeros, 107 | * empty lists, lists with repeated elements, etc. 108 | * 109 | * It is allowed to have multiple `assert` statements inside one test, 110 | * however it is recommended to write an individual `test` statement for 111 | * every tested aspect of a method. 112 | */ 113 | test("sum of a few numbers") { 114 | assert(sum(List(1,2,0)) === 3) 115 | } 116 | test("max of a few numbers") { 117 | assert(max(List(3, 7, 2)) === 7) 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /nodescala/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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] = Future.delay(20 seconds) continueWith { 29 | _ => "Server timeout!" 30 | } 31 | 32 | // TO IMPLEMENT 33 | // 4. create a future that completes when either 20 seconds elapse 34 | // or the user enters some text and presses ENTER 35 | val terminationRequested: Future[String] = Future.any(List(userInterrupted, timeOut)) 36 | 37 | // TO IMPLEMENT 38 | // 5. unsubscribe from the server 39 | terminationRequested onSuccess { 40 | case msg => 41 | println(msg) 42 | myServerSubscription.unsubscribe() 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /nodescala/src/main/scala/nodescala/nodescala.scala: -------------------------------------------------------------------------------- 1 | package nodescala 2 | 3 | import com.sun.net.httpserver._ 4 | import nodescala.NodeScala.Listener 5 | import scala.concurrent._ 6 | import scala.concurrent.duration._ 7 | import ExecutionContext.Implicits.global 8 | import scala.async.Async.{async, await} 9 | import scala.collection._ 10 | import scala.collection.JavaConversions._ 11 | import java.util.concurrent.{Executor, ThreadPoolExecutor, TimeUnit, LinkedBlockingQueue} 12 | import com.sun.net.httpserver.{HttpExchange, HttpHandler, HttpServer} 13 | import java.net.InetSocketAddress 14 | 15 | /** Contains utilities common to the NodeScala© framework. 16 | */ 17 | trait NodeScala { 18 | 19 | import NodeScala._ 20 | 21 | def port: Int 22 | 23 | def createListener(relativePath: String): Listener 24 | 25 | /** Uses the response object to respond to the write the response back. 26 | * The response should be written back in parts, and the method should 27 | * occasionally check that server was not stopped, otherwise a very long 28 | * response may take very long to finish. 29 | * 30 | * @param exchange the exchange used to write the response back 31 | * @param token the cancellation token 32 | * @param response the response to write back 33 | */ 34 | private def respond(exchange: Exchange, token: CancellationToken, response: Response): Unit = { 35 | while (token.nonCancelled && response.hasNext) { 36 | exchange.write(response.next()) 37 | } 38 | 39 | exchange.close() 40 | } 41 | 42 | /** A server: 43 | * 1) creates and starts an http listener 44 | * 2) creates a cancellation token (hint: use one of the `Future` companion methods) 45 | * 3) as long as the token is not cancelled and there is a request from the http listener, 46 | * asynchronously process that request using the `respond` method 47 | * 48 | * @param relativePath a relative path on which to start listening 49 | * @param handler a function mapping a request to a response 50 | * @return a subscription that can stop the server and all its asynchronous operations *entirely* 51 | */ 52 | def start(relativePath: String)(handler: Request => Response): Subscription = { 53 | val listener = createListener(relativePath) 54 | val listenerSubscription = listener.start() 55 | val processSubscription = Future.run() { ct => 56 | async { 57 | while (ct.nonCancelled) { 58 | val (req, xchg) = await { 59 | listener.nextRequest() 60 | } 61 | respond(xchg, ct, handler(req)) 62 | } 63 | } 64 | } 65 | 66 | Subscription(listenerSubscription, processSubscription) 67 | } 68 | 69 | } 70 | 71 | 72 | object NodeScala { 73 | 74 | /** A request is a multimap of headers, where each header is a key-value pair of strings. 75 | */ 76 | type Request = Map[String, List[String]] 77 | 78 | /** A response consists of a potentially long string (e.g. a data file). 79 | * To be able to process this string in parts, the response is encoded 80 | * as an iterator over a subsequences of the response string. 81 | */ 82 | type Response = Iterator[String] 83 | 84 | /** Used to write the response to the request. 85 | */ 86 | trait Exchange { 87 | /** Writes to the output stream of the exchange. 88 | */ 89 | def write(s: String): Unit 90 | 91 | /** Communicates that the response has ended and that there 92 | * will be no further writes. 93 | */ 94 | def close(): Unit 95 | 96 | def request: Request 97 | 98 | } 99 | 100 | object Exchange { 101 | def apply(exchange: HttpExchange) = new Exchange { 102 | val os = exchange.getResponseBody 103 | exchange.sendResponseHeaders(200, 0L) 104 | 105 | def write(s: String) = os.write(s.getBytes) 106 | 107 | def close() = os.close() 108 | 109 | def request: Request = { 110 | val headers = for ((k, vs) <- exchange.getRequestHeaders) yield (k, vs.toList) 111 | immutable.Map() ++ headers 112 | } 113 | } 114 | } 115 | 116 | trait Listener { 117 | def port: Int 118 | 119 | def relativePath: String 120 | 121 | def start(): Subscription 122 | 123 | def createContext(handler: Exchange => Unit): Unit 124 | 125 | def removeContext(): Unit 126 | 127 | /** This Method: 128 | * 1) constructs an uncompleted promise 129 | * 2) installs an asynchronous `HttpHandler` to the `server` 130 | * that deregisters itself 131 | * and then completes the promise with a request when `handle` method is invoked 132 | * 3) returns the future with the request 133 | * 134 | * @return the promise holding the pair of a request and an exchange object 135 | */ 136 | def nextRequest(): Future[(Request, Exchange)] = { 137 | val p = Promise[(Request, Exchange)]() 138 | 139 | createContext(xchg => { 140 | val req = xchg.request 141 | removeContext() 142 | p.success((req, xchg)) 143 | }) 144 | 145 | p.future 146 | } 147 | } 148 | 149 | object Listener { 150 | 151 | class Default(val port: Int, val relativePath: String) extends Listener { 152 | private val s = HttpServer.create(new InetSocketAddress(port), 0) 153 | private val executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue) 154 | s.setExecutor(executor) 155 | 156 | def start() = { 157 | s.start() 158 | new Subscription { 159 | def unsubscribe() = { 160 | s.stop(0) 161 | executor.shutdown() 162 | } 163 | } 164 | } 165 | 166 | def createContext(handler: Exchange => Unit) = s.createContext(relativePath, new HttpHandler { 167 | def handle(httpxchg: HttpExchange) = handler(Exchange(httpxchg)) 168 | }) 169 | 170 | def removeContext() = s.removeContext(relativePath) 171 | } 172 | 173 | } 174 | 175 | /** The standard server implementation. 176 | */ 177 | class Default(val port: Int) extends NodeScala { 178 | def createListener(relativePath: String) = new Listener.Default(port, relativePath) 179 | } 180 | 181 | } 182 | -------------------------------------------------------------------------------- /nodescala/src/main/scala/nodescala/package.scala: -------------------------------------------------------------------------------- 1 | import scala.language.postfixOps 2 | import scala.io.StdIn 3 | import scala.util._ 4 | import scala.util.control.NonFatal 5 | import scala.concurrent._ 6 | import scala.concurrent.duration._ 7 | import ExecutionContext.Implicits.global 8 | import scala.async.Async.{async, await} 9 | 10 | /** Contains basic data types, data structures and `Future` extensions. 11 | */ 12 | package object nodescala { 13 | 14 | /** Adds extensions methods to the `Future` companion object. 15 | */ 16 | implicit class FutureCompanionOps(val f: Future.type) extends AnyVal { 17 | 18 | /** Returns a future that is always completed with `value`. 19 | */ 20 | def always[T](value: T): Future[T] = Future(value) 21 | 22 | /** Returns a future that is never completed. 23 | * 24 | * This future may be useful when testing if timeout logic works correctly. 25 | */ 26 | def never[T]: Future[T] = Promise[T]().future 27 | 28 | /** Given a list of futures `fs`, returns the future holding the list of values of all the futures from `fs`. 29 | * The returned future is completed only once all of the futures in `fs` have been completed. 30 | * The values in the list are in the same order as corresponding futures `fs`. 31 | * If any of the futures `fs` fails, the resulting future also fails. 32 | */ 33 | def all[T](fs: List[Future[T]]): Future[List[T]] = fs match { 34 | case Nil => Future(Nil) 35 | case x :: xs => 36 | for { 37 | v <- x 38 | vs <- all(xs) 39 | } yield v :: vs 40 | } 41 | 42 | /** Given a list of futures `fs`, returns the future holding the value of the future from `fs` that completed first. 43 | * If the first completing future in `fs` fails, then the result is failed as well. 44 | * 45 | * E.g.: 46 | * 47 | * Future.any(List(Future { 1 }, Future { 2 }, Future { throw new Exception })) 48 | * 49 | * may return a `Future` succeeded with `1`, `2` or failed with an `Exception`. 50 | */ 51 | def any[T](fs: List[Future[T]]): Future[T] = { 52 | val p = Promise[T]() 53 | 54 | fs foreach (_ onComplete p.tryComplete) 55 | 56 | p.future 57 | } 58 | 59 | /** Returns a future with a unit value that is completed after time `t`. 60 | */ 61 | def delay(t: Duration): Future[Unit] = Future { 62 | blocking { 63 | Thread.sleep(t toMillis) 64 | } 65 | } 66 | 67 | /** Completes this future with user input. 68 | */ 69 | def userInput(message: String): Future[String] = Future { 70 | blocking { 71 | StdIn.readLine(message) 72 | } 73 | } 74 | 75 | /** Creates a cancellable context for an execution and runs it. 76 | */ 77 | def run()(f: CancellationToken => Future[Unit]): Subscription = { 78 | val cts = CancellationTokenSource() 79 | f(cts.cancellationToken) 80 | cts 81 | } 82 | 83 | } 84 | 85 | /** Adds extension methods to future objects. 86 | */ 87 | implicit class FutureOps[T](val f: Future[T]) extends AnyVal { 88 | 89 | /** Returns the result of this future if it is completed now. 90 | * Otherwise, throws a `NoSuchElementException`. 91 | * 92 | * Note: This method does not wait for the result. 93 | * It is thus non-blocking. 94 | * However, it is also non-deterministic -- it may throw or return a value 95 | * depending on the current state of the `Future`. 96 | */ 97 | def now: T = if (f.isCompleted) Await.result(f, 0 nanosecond) else throw new NoSuchElementException 98 | 99 | /** Continues the computation of this future by taking the current future 100 | * and mapping it into another future. 101 | * 102 | * The function `cont` is called only after the current future completes. 103 | * The resulting future contains a value returned by `cont`. 104 | */ 105 | def continueWith[S](cont: Future[T] => S): Future[S] = { 106 | val p = Promise[S]() 107 | 108 | f onComplete { _ => 109 | p tryComplete Try(cont(f)) 110 | } 111 | 112 | p.future 113 | } 114 | 115 | /** Continues the computation of this future by taking the result 116 | * of the current future and mapping it into another future. 117 | * 118 | * The function `cont` is called only after the current future completes. 119 | * The resulting future contains a value returned by `cont`. 120 | */ 121 | def continue[S](cont: Try[T] => S): Future[S] = { 122 | val p = Promise[S]() 123 | 124 | f onComplete { t => 125 | p tryComplete Try(cont(t)) 126 | } 127 | 128 | p.future 129 | } 130 | 131 | } 132 | 133 | /** Subscription objects are used to be able to unsubscribe 134 | * from some event source. 135 | */ 136 | trait Subscription { 137 | def unsubscribe(): Unit 138 | } 139 | 140 | object Subscription { 141 | /** Given two subscriptions `s1` and `s2` returns a new composite subscription 142 | * such that when the new composite subscription cancels both `s1` and `s2` 143 | * when `unsubscribe` is called. 144 | */ 145 | def apply(s1: Subscription, s2: Subscription) = new Subscription { 146 | def unsubscribe() { 147 | s1.unsubscribe() 148 | s2.unsubscribe() 149 | } 150 | } 151 | } 152 | 153 | /** Used to check if cancellation was requested. 154 | */ 155 | trait CancellationToken { 156 | def isCancelled: Boolean 157 | 158 | def nonCancelled = !isCancelled 159 | } 160 | 161 | /** The `CancellationTokenSource` is a special kind of `Subscription` that 162 | * returns a `cancellationToken` which is cancelled by calling `unsubscribe`. 163 | * 164 | * After calling `unsubscribe` once, the associated `cancellationToken` will 165 | * forever remain cancelled -- its `isCancelled` will return `false`. 166 | */ 167 | trait CancellationTokenSource extends Subscription { 168 | def cancellationToken: CancellationToken 169 | } 170 | 171 | /** Creates cancellation token sources. 172 | */ 173 | object CancellationTokenSource { 174 | /** Creates a new `CancellationTokenSource`. 175 | */ 176 | def apply() = new CancellationTokenSource { 177 | val p = Promise[Unit]() 178 | val cancellationToken = new CancellationToken { 179 | def isCancelled = p.future.value != None 180 | } 181 | 182 | def unsubscribe() { 183 | p.trySuccess(()) 184 | } 185 | } 186 | } 187 | 188 | } 189 | 190 | -------------------------------------------------------------------------------- /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 | test("CancellationTokenSource should allow stopping the computation") { 35 | val cts = CancellationTokenSource() 36 | val ct = cts.cancellationToken 37 | val p = Promise[String]() 38 | 39 | async { 40 | while (ct.nonCancelled) { 41 | // do work 42 | } 43 | 44 | p.success("done") 45 | } 46 | 47 | cts.unsubscribe() 48 | assert(Await.result(p.future, 1 second) == "done") 49 | } 50 | 51 | class DummyExchange(val request: Request) extends Exchange { 52 | @volatile var response = "" 53 | val loaded = Promise[String]() 54 | 55 | def write(s: String) { 56 | response += s 57 | } 58 | 59 | def close() { 60 | loaded.success(response) 61 | } 62 | } 63 | 64 | class DummyListener(val port: Int, val relativePath: String) extends NodeScala.Listener { 65 | self => 66 | 67 | @volatile private var started = false 68 | var handler: Exchange => Unit = null 69 | 70 | def createContext(h: Exchange => Unit) = this.synchronized { 71 | assert(started, "is server started?") 72 | handler = h 73 | } 74 | 75 | def removeContext() = this.synchronized { 76 | assert(started, "is server started?") 77 | handler = null 78 | } 79 | 80 | def start() = self.synchronized { 81 | started = true 82 | new Subscription { 83 | def unsubscribe() = self.synchronized { 84 | started = false 85 | } 86 | } 87 | } 88 | 89 | def emit(req: Request) = { 90 | val exchange = new DummyExchange(req) 91 | if (handler != null) handler(exchange) 92 | exchange 93 | } 94 | } 95 | 96 | class DummyServer(val port: Int) extends NodeScala { 97 | self => 98 | val listeners = mutable.Map[String, DummyListener]() 99 | 100 | def createListener(relativePath: String) = { 101 | val l = new DummyListener(port, relativePath) 102 | listeners(relativePath) = l 103 | l 104 | } 105 | 106 | def emit(relativePath: String, req: Request) = this.synchronized { 107 | val l = listeners(relativePath) 108 | l.emit(req) 109 | } 110 | } 111 | 112 | test("Server should serve requests") { 113 | val dummy = new DummyServer(8191) 114 | val dummySubscription = dummy.start("/testDir") { 115 | request => for (kv <- request.iterator) yield (kv + "\n").toString 116 | } 117 | 118 | // wait until server is really installed 119 | Thread.sleep(500) 120 | 121 | def test(req: Request) { 122 | val webpage = dummy.emit("/testDir", req) 123 | val content = Await.result(webpage.loaded.future, 1 second) 124 | val expected = (for (kv <- req.iterator) yield (kv + "\n").toString).mkString 125 | assert(content == expected, s"'$content' vs. '$expected'") 126 | } 127 | 128 | test(immutable.Map("StrangeRequest" -> List("Does it work?"))) 129 | test(immutable.Map("StrangeRequest" -> List("It works!"))) 130 | test(immutable.Map("WorksForThree" -> List("Always works. Trust me."))) 131 | 132 | dummySubscription.unsubscribe() 133 | } 134 | 135 | } 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /quickcheck/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /quickcheck/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("ch.epfl.lamp" % "sbt-coursera" % "0.5") 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | property("gen1") = forAll { h: H => 18 | val m = realMin(h) 19 | findMin(insert(m, h)) == m 20 | } 21 | 22 | // If you insert any two elements into an empty heap, 23 | // finding the minimum of the resulting heap should get the smallest of the two elements back. 24 | property("min2") = forAll { (a: Int, b: Int) => 25 | val h1 = insert(a, empty) 26 | val h2 = insert(b, h1) 27 | findMin(h2) == math.min(a, b) 28 | } 29 | 30 | // If you insert an element into an empty heap, then delete the minimum, the resulting heap should be empty. 31 | property("deleteMin1") = forAll { a: Int => 32 | val h = insert(a, empty) 33 | val hh = deleteMin(h) 34 | isEmpty(hh) 35 | } 36 | 37 | // Given any heap, you should get a sorted sequence of elements when continually finding and deleting minima. 38 | // (Hint: recursion and helper functions are your friends.) 39 | property("sorted") = forAll { h: H => 40 | val l = genList(h) 41 | isSorted(l) 42 | } 43 | 44 | // Finding a minimum of the melding of any two heaps should return a minimum of one or the other. 45 | property("minimum of two") = forAll { (h1: H, h2: H) => 46 | val m = meld(h1, h2) 47 | if (isEmpty(h1) && isEmpty(h2)) realMin(m) == 0 48 | else if (!isEmpty(h1) && isEmpty(h2)) findMin(m) == findMin(h1) 49 | else if (isEmpty(h1) && !isEmpty(h2)) findMin(m) == findMin(h2) 50 | else if (findMin(h1) <= findMin(h2)) findMin(m) == findMin(h1) 51 | else findMin(m) == findMin(h2) 52 | } 53 | 54 | property("meld sorted") = forAll { (h1: H, h2: H) => 55 | val m = meld(h1, h2) 56 | val l = genList(m) 57 | isSorted(l) 58 | } 59 | 60 | property("deleteMin2") = forAll { (a: Int, b: Int) => 61 | val h1 = insert(a, empty) 62 | val h2 = insert(b, h1) 63 | deleteMin(h2) == insert(math.max(a, b), empty) 64 | } 65 | 66 | property("deleteMin3") = forAll { l: List[Int] => 67 | val ll = l.sorted 68 | val h = ll.foldLeft(empty)((accu, curr) => insert(curr, accu)) 69 | genList(h) == ll 70 | } 71 | 72 | property("deleteMin4") = forAll { l: List[Int] => 73 | if (l.size == 0) true 74 | else { 75 | val ll = l.sorted 76 | val h = ll.foldLeft(empty)((accu, curr) => insert(curr, accu)) 77 | ll.tail == genList(deleteMin(h)) 78 | } 79 | } 80 | 81 | def realMin(h: H): Int = if (isEmpty(h)) 0 else findMin(h) 82 | 83 | def genList(h: H): List[Int] = { 84 | if (isEmpty(h)) Nil 85 | else findMin(h) :: genList(deleteMin(h)) 86 | } 87 | 88 | def isSorted(xs: List[Int]): Boolean = xs match { 89 | case Nil => true 90 | case x :: xss => if (xss.isEmpty) true else (x <= xss.head) && isSorted(xss) 91 | } 92 | 93 | lazy val genHeap: Gen[H] = for { 94 | a <- arbitrary[Int] 95 | h <- oneOf(const(empty), genHeap) 96 | } yield insert(a, h) 97 | 98 | implicit lazy val arbHeap: Arbitrary[H] = Arbitrary(genHeap) 99 | } 100 | -------------------------------------------------------------------------------- /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 ch.epfl.lamp.grading.GradingSuite 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 | -------------------------------------------------------------------------------- /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.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /suggestions/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=0.13.7 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /suggestions/src/main/resources/suggestions/wiki-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/izzyleung/Principles-Of-Reactive-Programming/16b4d032371dc947ddc423f4bf4e387a27bed721/suggestions/src/main/resources/suggestions/wiki-icon.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.{Subscription, 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 | * @return an observable with a stream of text field updates 52 | */ 53 | def textValues: Observable[String] = Observable.create(observer => { 54 | field subscribe { 55 | case ValueChanged(f) => observer.onNext(f.text) 56 | } 57 | 58 | Subscription(field) 59 | }) 60 | 61 | } 62 | 63 | implicit class ButtonOps(button: Button) { 64 | 65 | /** Returns a stream of button clicks. 66 | * 67 | * @return an observable with a stream of buttons that have been clicked 68 | */ 69 | def clicks: Observable[Button] = Observable.create(observer => { 70 | button subscribe { 71 | case ButtonClicked(b) => observer.onNext(b) 72 | } 73 | 74 | Subscription(button) 75 | }) 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /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.control.NonFatal 11 | import scala.util.{Try, Success, Failure} 12 | import rx.subscriptions.CompositeSubscription 13 | import rx.lang.scala.Observable 14 | import observablex._ 15 | import search._ 16 | 17 | trait WikipediaApi { 18 | 19 | /** Returns a `Future` with a list of possible completions for a search `term`. 20 | */ 21 | def wikipediaSuggestion(term: String): Future[List[String]] 22 | 23 | /** Returns a `Future` with the contents of the Wikipedia page for the given search `term`. 24 | */ 25 | def wikipediaPage(term: String): Future[String] 26 | 27 | /** Returns an `Observable` with a list of possible completions for a search `term`. 28 | */ 29 | def wikiSuggestResponseStream(term: String): Observable[List[String]] = ObservableEx(wikipediaSuggestion(term)).timedOut(1L) 30 | 31 | /** Returns an `Observable` with the contents of the Wikipedia page for the given search `term`. 32 | */ 33 | def wikiPageResponseStream(term: String): Observable[String] = ObservableEx(wikipediaPage(term)).timedOut(1L) 34 | 35 | implicit class StringObservableOps(obs: Observable[String]) { 36 | 37 | /** Given a stream of search terms, returns a stream of search terms with spaces replaced by underscores. 38 | * 39 | * E.g. `"erik", "erik meijer", "martin` should become `"erik", "erik_meijer", "martin"` 40 | */ 41 | def sanitized: Observable[String] = obs.map(s => s.map(c => if (c == ' ') '_' else c)) 42 | 43 | } 44 | 45 | implicit class ObservableOps[T](obs: Observable[T]) { 46 | 47 | /** Given an observable that can possibly be completed with an error, returns a new observable 48 | * with the same values wrapped into `Success` and the potential error wrapped into `Failure`. 49 | * 50 | * E.g. `1, 2, 3, !Exception!` should become `Success(1), Success(2), Success(3), Failure(Exception), !TerminateStream!` 51 | */ 52 | def recovered: Observable[Try[T]] = obs map (Success(_)) onErrorReturn (Failure(_)) 53 | 54 | /** Emits the events from the `obs` observable, until `totalSec` seconds have elapsed. 55 | * 56 | * After `totalSec` seconds, if `obs` is not yet completed, the result observable becomes completed. 57 | * 58 | * Note: uses the existing combinators on observables. 59 | */ 60 | def timedOut(totalSec: Long): Observable[T] = obs takeUntil Observable.interval(totalSec seconds) 61 | 62 | /** Given a stream of events `obs` and a method `requestMethod` to map a request `T` into 63 | * a stream of responses `S`, returns a stream of all the responses wrapped into a `Try`. 64 | * The elements of the response stream should reflect the order of their corresponding events in `obs`. 65 | * 66 | * E.g. given a request stream: 67 | * 68 | * 1, 2, 3, 4, 5 69 | * 70 | * And a request method: 71 | * 72 | * num => if (num != 4) Observable.just(num) else Observable.error(new Exception) 73 | * 74 | * We should, for example, get: 75 | * 76 | * Success(1), Success(2), Success(3), Failure(new Exception), Success(5) 77 | * 78 | * 79 | * Similarly: 80 | * 81 | * Observable(1, 2, 3).concatRecovered(num => Observable(num, num, num)) 82 | * 83 | * should return: 84 | * 85 | * Observable(Success(1), Succeess(1), Succeess(1), Succeess(2), Succeess(2), Succeess(2), Succeess(3), Succeess(3), Succeess(3)) 86 | */ 87 | def concatRecovered[S](requestMethod: T => Observable[S]): Observable[Try[S]] = obs flatMap (requestMethod(_) recovered) 88 | } 89 | 90 | } 91 | 92 | -------------------------------------------------------------------------------- /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 | 45 | import javax.swing.border._ 46 | 47 | border = new EtchedBorder(EtchedBorder.LOWERED) 48 | editable = false 49 | peer.setContentType("text/html") 50 | } 51 | 52 | contents = new BoxPanel(orientation = Vertical) { 53 | border = EmptyBorder(top = 5, left = 5, bottom = 5, right = 5) 54 | contents += new BoxPanel(orientation = Horizontal) { 55 | contents += new BoxPanel(orientation = Vertical) { 56 | maximumSize = new Dimension(240, 900) 57 | border = EmptyBorder(top = 10, left = 10, bottom = 10, right = 10) 58 | contents += new BoxPanel(orientation = Horizontal) { 59 | maximumSize = new Dimension(640, 30) 60 | border = EmptyBorder(top = 5, left = 0, bottom = 5, right = 0) 61 | contents += searchTermField 62 | } 63 | contents += new ScrollPane(suggestionList) 64 | contents += new BorderPanel { 65 | maximumSize = new Dimension(640, 30) 66 | add(button, BorderPanel.Position.Center) 67 | } 68 | } 69 | contents += new ScrollPane(editorpane) 70 | } 71 | contents += status 72 | } 73 | 74 | val eventScheduler = SchedulerEx.SwingEventThreadScheduler 75 | 76 | /** 77 | * Observables 78 | * You may find the following methods useful when manipulating GUI elements: 79 | * `myListView.listData = aList` : sets the content of `myListView` to `aList` 80 | * `myTextField.text = "react"` : sets the content of `myTextField` to "react" 81 | * `myListView.selection.items` returns a list of selected items from `myListView` 82 | * `myEditorPane.text = "act"` : sets the content of `myEditorPane` to "act" 83 | */ 84 | val searchTerms: Observable[String] = searchTermField.textValues 85 | 86 | val suggestions: Observable[Try[List[String]]] = searchTerms.sanitized concatRecovered wikiSuggestResponseStream 87 | 88 | val suggestionSubscription: Subscription = suggestions.observeOn(eventScheduler) subscribe { 89 | x => x match { 90 | case Success(result) => suggestionList.listData = result 91 | case Failure(err) => status.text = err.getMessage 92 | } 93 | } 94 | 95 | val selections: Observable[String] = button.clicks flatMap { _ => 96 | if (suggestionList.selection.items.size == 0) Observable.error(new Exception("NoSelection")) 97 | else Observable.just(suggestionList.selection.items(0)) 98 | } 99 | 100 | val pages: Observable[Try[String]] = selections.sanitized concatRecovered wikiPageResponseStream 101 | 102 | val pageSubscription: Subscription = pages.observeOn(eventScheduler) subscribe { 103 | x => x match { 104 | case Success(result) => editorpane.text = result 105 | case Failure(err) => editorpane.text = err.getMessage 106 | } 107 | } 108 | } 109 | 110 | } 111 | 112 | 113 | trait ConcreteWikipediaApi extends WikipediaApi { 114 | def wikipediaSuggestion(term: String) = Search.wikipediaSuggestion(term) 115 | 116 | def wikipediaPage(term: String) = Search.wikipediaPage(term) 117 | } 118 | 119 | 120 | trait ConcreteSwingApi extends SwingApi { 121 | type ValueChanged = scala.swing.event.ValueChanged 122 | 123 | object ValueChanged { 124 | def unapply(x: Event) = x match { 125 | case vc: ValueChanged => Some(vc.source.asInstanceOf[TextField]) 126 | case _ => None 127 | } 128 | } 129 | 130 | type ButtonClicked = scala.swing.event.ButtonClicked 131 | 132 | object ButtonClicked { 133 | def unapply(x: Event) = x match { 134 | case bc: ButtonClicked => Some(bc.source.asInstanceOf[Button]) 135 | case _ => None 136 | } 137 | } 138 | 139 | type TextField = scala.swing.TextField 140 | type Button = scala.swing.Button 141 | } 142 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /suggestions/src/main/scala/suggestions/package.scala: -------------------------------------------------------------------------------- 1 | package object suggestions { 2 | 3 | def log(x: Any) = println(x) 4 | 5 | } 6 | -------------------------------------------------------------------------------- /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 | trait WikipediaService { 74 | @GET("/w/api.php??action=opensearch&format=json&limit=15") 75 | def suggestions(@Query("search") term: String, callback: Callback[Array[AnyRef]]): Unit 76 | 77 | @GET("/w/api.php??action=parse&format=json&prop=text§ion=0") 78 | def page(@Query("page") term: String, callback: Callback[Page]): Unit 79 | } 80 | 81 | val restAdapter = new RestAdapter.Builder().setServer("http://en.wikipedia.org").build() 82 | 83 | val service = restAdapter.create(classOf[WikipediaService]) 84 | 85 | def callbackFuture[T]: (Callback[T], Future[T]) = { 86 | val p = Promise[T]() 87 | val cb = new Callback[T] { 88 | def success(t: T, response: Response) = { 89 | p success t 90 | } 91 | def failure(error: RetrofitError) = { 92 | p failure error 93 | } 94 | } 95 | 96 | (cb, p.future) 97 | } 98 | 99 | def wikipediaSuggestionRetrofit(term: String): Future[List[String]] = { 100 | async { 101 | val (cb, f) = callbackFuture[Array[AnyRef]] 102 | service.suggestions(term, cb) 103 | val result = await { f } 104 | val arraylist = result(1).asInstanceOf[java.util.List[String]] 105 | 106 | arraylist.asScala.toList 107 | } 108 | } 109 | 110 | def wikipediaPageRetrofit(term: String): Future[String] = { 111 | async { 112 | val (cb, f) = callbackFuture[Page] 113 | service.page(term, cb) 114 | val result = await { f } 115 | result.parse.text.all 116 | } 117 | } 118 | 119 | def wikipediaSuggestion(term: String): Future[List[String]] = wikipediaSuggestionRetrofit(term) 120 | 121 | def wikipediaPage(term: String): Future[String] = wikipediaPageRetrofit(term) 122 | 123 | } 124 | 125 | -------------------------------------------------------------------------------- /suggestions/src/test/scala/suggestions/SwingApiTest.scala: -------------------------------------------------------------------------------- 1 | package suggestions 2 | 3 | import scala.collection._ 4 | import scala.concurrent._ 5 | import scala.concurrent.ExecutionContext.Implicits.global 6 | import scala.util.{Try, Success, Failure} 7 | import scala.swing.event.Event 8 | import scala.swing.Reactions.Reaction 9 | import rx.lang.scala._ 10 | import org.scalatest._ 11 | import gui._ 12 | 13 | import org.junit.runner.RunWith 14 | import org.scalatest.junit.JUnitRunner 15 | 16 | @RunWith(classOf[JUnitRunner]) 17 | class SwingApiTest extends FunSuite { 18 | 19 | object swingApi extends SwingApi { 20 | class ValueChanged(val textField: TextField) extends Event 21 | 22 | object ValueChanged { 23 | def unapply(x: Event) = x match { 24 | case vc: ValueChanged => Some(vc.textField) 25 | case _ => None 26 | } 27 | } 28 | 29 | class ButtonClicked(val source: Button) extends Event 30 | 31 | object ButtonClicked { 32 | def unapply(x: Event) = x match { 33 | case bc: ButtonClicked => Some(bc.source) 34 | case _ => None 35 | } 36 | } 37 | 38 | class Component { 39 | private val subscriptions = mutable.Set[Reaction]() 40 | def subscribe(r: Reaction) { 41 | subscriptions add r 42 | } 43 | def unsubscribe(r: Reaction) { 44 | subscriptions remove r 45 | } 46 | def publish(e: Event) { 47 | for (r <- subscriptions) r(e) 48 | } 49 | } 50 | 51 | class TextField extends Component { 52 | private var _text = "" 53 | def text = _text 54 | def text_=(t: String) { 55 | _text = t 56 | publish(new ValueChanged(this)) 57 | } 58 | } 59 | 60 | class Button extends Component { 61 | def click() { 62 | publish(new ButtonClicked(this)) 63 | } 64 | } 65 | } 66 | 67 | import swingApi._ 68 | 69 | test("SwingApi should emit text field values to the observable") { 70 | val textField = new swingApi.TextField 71 | val values = textField.textValues 72 | 73 | val observed = mutable.Buffer[String]() 74 | val sub = values subscribe { 75 | observed += _ 76 | } 77 | 78 | // write some text now 79 | textField.text = "T" 80 | textField.text = "Tu" 81 | textField.text = "Tur" 82 | textField.text = "Turi" 83 | textField.text = "Turin" 84 | textField.text = "Turing" 85 | 86 | assert(observed == Seq("T", "Tu", "Tur", "Turi", "Turin", "Turing"), observed) 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /suggestions/src/test/scala/suggestions/WikipediaApiTest.scala: -------------------------------------------------------------------------------- 1 | package suggestions 2 | 3 | 4 | import language.postfixOps 5 | import scala.concurrent._ 6 | import scala.concurrent.duration._ 7 | import scala.concurrent.ExecutionContext.Implicits.global 8 | import scala.util.{Try, Success, Failure} 9 | import rx.lang.scala._ 10 | import org.scalatest._ 11 | import gui._ 12 | 13 | import org.junit.runner.RunWith 14 | import org.scalatest.junit.JUnitRunner 15 | 16 | 17 | @RunWith(classOf[JUnitRunner]) 18 | class WikipediaApiTest extends FunSuite { 19 | 20 | object mockApi extends WikipediaApi { 21 | def wikipediaSuggestion(term: String) = Future { 22 | if (term.head.isLetter) { 23 | for (suffix <- List(" (Computer Scientist)", " (Footballer)")) yield term + suffix 24 | } else { 25 | List(term) 26 | } 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 | 54 | test("WikipediaApi should correctly use concatRecovered") { 55 | val requests = Observable.just(1, 2, 3) 56 | val remoteComputation = (n: Int) => Observable.just(0 to n: _*) 57 | val responses = requests concatRecovered remoteComputation 58 | val sum = responses.foldLeft(0) { (acc, tn) => 59 | tn match { 60 | case Success(n) => acc + n 61 | case Failure(t) => throw t 62 | } 63 | } 64 | var total = -1 65 | val sub = sum.subscribe { 66 | s => total = s 67 | } 68 | assert(total == (1 + 1 + 2 + 1 + 2 + 3), s"Sum: $total") 69 | } 70 | 71 | } 72 | --------------------------------------------------------------------------------