Skip to content
Snippets Groups Projects
Commit 9b285ed1 authored by Matt Bovel's avatar Matt Bovel
Browse files

Add solutions of exercises 4

parent c8ba76dc
No related branches found
No related tags found
No related merge requests found
.vscode
.metals
.bloop
.bsp
target
metals.sbt
build.properties
project/project
version = "3.4.0"
runner.dialect = scala3
rewrite.scala3.convertToNewSyntax = true
rewrite.scala3.removeOptionalBraces = true
# Exercise Session 4, Solutions
- Problem 1: [Problem1.scala](src/main/scala/Problem1.scala) and [Problem1Test.scala](src/test/scala/Problem1Test.scala)
- Problem 2: [Problem2.scala](src/main/scala/Problem2.scala)
val scala3Version = "3.1.2"
val akkaVersion = "2.6.19"
lazy val root = project
.in(file("."))
.settings(
name := "code",
version := "0.1.0-SNAPSHOT",
scalaVersion := scala3Version,
fork := true,
javaOptions ++= Seq(
"-Dakka.loglevel=Debug",
"-Dakka.actor.debug.receive=on"
),
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-actor" % akkaVersion,
"com.typesafe.akka" %% "akka-testkit" % akkaVersion,
"junit" % "junit" % "4.13" % Test,
"com.github.sbt" % "junit-interface" % "0.13.3" % Test
),
Test / testOptions += Tests.Argument(TestFrameworks.JUnit)
)
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6")
import scala.util.{Try, Success, Failure}
import scala.concurrent.ExecutionContext
import java.util.concurrent.Future
import java.util.concurrent.atomic.AtomicReference
// Test using `sbt "testOnly Problem1Test"`
// See tests in Problem1Test.scala
trait MyFuture[+T]:
def onComplete(callback: Try[T] => Unit): Unit
extension [T](self: MyFuture[T])
def map[S](f: T => S): MyFuture[S] =
new MyFuture:
def onComplete(callback: Try[S] => Unit): Unit =
self.onComplete {
case Success(v) => callback(Success(f(v)))
case Failure(e) => callback(Failure(e))
}
def filter(p: T => Boolean): MyFuture[T] =
new MyFuture:
def onComplete(callback: Try[T] => Unit): Unit =
self.onComplete {
case Success(v) =>
if p(v) then callback(Success(v))
else callback(Failure(new NoSuchElementException()))
case Failure(e) => callback(Failure(e))
}
import akka.actor.{Actor, Props, ActorSystem, ActorRef, ActorLogging}
import akka.testkit.{TestKit, ImplicitSender}
import akka.event.LoggingReceive
// Run using `sbt "runMain problem2"`
/** Type of messages exchanged between our Actors.
*
* Note: enumerations are the Scala 3 idiomatic syntax to define algebraic data
* types (ADTs). The code below is desugared to something equivalent to:
*
* ```
* trait Message
* case class Request(computation: () => Unit) extends Message
* object Ready extends Message
* ```
*
* which is the syntax used in the lecture videos.
*
* Read also:
* - Translation of Enums and ADTs:
* https://docs.scala-lang.org/scala3/reference/enums/desugarEnums.html
* - Enums slides from CS210:
* https://gitlab.epfl.ch/lamp/cs210/-/blob/master/slides/progfun1-4-4.pdf
*/
enum Message:
case Request(computation: () => Unit)
case Ready
import Message.*
class Coordinator extends Actor:
var availableWorkers: List[ActorRef] = Nil
var pendingRequests: List[Request] = Nil
override def receive = LoggingReceive {
case Ready =>
if pendingRequests.isEmpty then
availableWorkers = availableWorkers :+ sender()
else
val request = pendingRequests.head
pendingRequests = pendingRequests.tail
sender() ! request
case request: Request =>
availableWorkers match
case worker :: rest =>
worker ! request
availableWorkers = rest
case Nil =>
pendingRequests = pendingRequests :+ request
}
class Worker(coordinator: ActorRef) extends Actor:
coordinator ! Ready
override def receive: Receive = LoggingReceive { case Request(f) =>
f()
coordinator ! Ready
}
@main def problem2 = new TestKit(ActorSystem("coordinator-workers"))
with ImplicitSender:
try
val coordinator = system.actorOf(Props(Coordinator()), "coordinator")
val workers = Seq.tabulate(4)(i =>
system.actorOf(Props(Worker(coordinator)), f"worker$i")
)
// Now, clients should be able to send requests to the coordinator…
coordinator ! Request(() => println(3 + 5))
coordinator ! Request(() => println(67 * 3))
// And so on...
finally shutdown(system)
import scala.util.{Try, Success, Failure}
import org.junit.Test
import org.junit.Assert.{assertEquals, fail}
class Problem1Test:
object MyFuture:
final def successful[T](value: T): MyFuture[T] =
new MyFuture:
def onComplete(callback: Try[T] => Unit): Unit =
callback(Success(value))
final def failed[T](error: Error): MyFuture[T] =
new MyFuture:
def onComplete(callback: Try[T] => Unit): Unit =
callback(Failure(error))
@Test
def mapWorksWithSuccess() =
MyFuture.successful(3).map(_ + 1).onComplete {
case Success(value) => assertEquals(4, value)
case _ => fail("Expected result to be a Success")
}
@Test
def mapWorksWithFailure() =
val error = new Error("Some error")
MyFuture.failed[Int](error).map(_ + 1).onComplete {
case Failure(actualError) => assertEquals(error, actualError)
case _ => fail("Expected result to be a Failure")
}
@Test
def filterWorksWithSuccessNotFilteredOut() =
MyFuture.successful(3).filter(_ == 3).onComplete {
case Success(value) => assertEquals(3, value)
case _ => fail("Expected result to be a Failure")
}
@Test
def filterWorksWithSuccessFilteredOut() =
MyFuture.successful(3).filter(_ == 4).onComplete {
case Failure(actualError: NoSuchElementException) => ()
case _ => fail("Expected result to be a NoSuchElementException exception")
}
@Test
def filterWorksWithFailure() =
val error = new Error("Some error")
MyFuture.failed[Int](error).filter(_ == 3).onComplete {
case Failure(actualError) => assertEquals(error, actualError)
case _ => fail("Expected result to be a Failure")
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment