Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • lara/cs206-demos
  • gcharles/cs206-demos
  • gambhir/cs206-demos
3 results
Show changes
Commits on Source (7)
......@@ -21,4 +21,4 @@ test:
tags:
- cs320
script:
- sbt "runMain lecture1.javaThreads; runMain lecture1.scalaThreadWrapper; runMain lecture1.ExampleThread; runMain lecture3.intersectionWrong; runMain lecture3.intersectionCorrect; runMain lecture3.intersectionNoSideEffect; runMain lecture3.parallelGraphContraction; runMain lecture3.parallelGraphContractionCorrect; runMain midterm22.mock1; runMain midterm22.part3; test; scalafmtCheck; Test / scalafmtCheck"
- sbt "runMain lecture1.javaThreads; runMain lecture1.scalaThreadWrapper; runMain lecture1.ExampleThread; runMain lecture3.intersectionWrong; runMain lecture3.intersectionCorrect; runMain lecture3.intersectionNoSideEffect; runMain lecture3.parallelGraphContraction; runMain lecture3.parallelGraphContractionCorrect; runMain midterm22.mock1; runMain midterm22.part3; runMain ed.patternMatching; runMain lecture13.askPatternDemo; test; runMain lecture13.becomeDemo; runMain ed.futuresForTranslation; runMain concpar20final03.concpar20final03; scalafmtCheck; Test / scalafmtCheck"
package concpar20final03
import scala.concurrent.{Await, Future}
import scala.concurrent.duration.Duration
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
def sequence1[A](fs: List[Future[A]]): Future[List[A]] =
fs match
case f :: fs =>
for
x <- f
xs <- sequence1(fs)
yield x :: xs
case Nil =>
Future.successful(Nil)
def sequence2[A](fs: List[Future[A]]): Future[List[A]] =
fs match
case f :: fs =>
f.flatMap(x => sequence2(fs).map(xs => x :: xs))
case Nil =>
Future.successful(Nil)
def sequence3[A](fs: List[Future[A]]): Future[List[A]] =
fs match
case f :: fs =>
f.transformWith {
case Failure(e) => Future.failed(e)
case Success(value) =>
sequence3(fs).transform {
case res: Failure[List[A]] => res
case Success(values) => Success(value :: values)
}
}
case Nil =>
Future.successful(Nil)
def sequence4[A](fs: List[Future[A]]): Future[List[A]] =
fs match
case f :: fs =>
val fsFuture: Future[List[A]] = sequence4(fs)
f.zip(fsFuture).map((fValue, fsValue) => fValue :: fsValue)
case Nil =>
Future.successful(Nil)
@main def concpar20final03 =
val xs = List(Future { 1 }, Future { 2 }, Future { 3 })
val ys = List(Future { 1 }, Future.failed(Error("BOOM")), Future { 3 })
println(Await.result(sequence1(xs), Duration.Inf))
// println(Await.result(sequence1(xs), Duration.Inf)) // Throws exception
println(Await.result(sequence2(xs), Duration.Inf))
// println(Await.result(sequence2(ys), Duration.Inf)) // Throws exception
println(Await.result(sequence3(xs), Duration.Inf))
// println(Await.result(sequence3(ys), Duration.Inf)) // Throws exception
println(Await.result(sequence4(xs), Duration.Inf))
// println(Await.result(sequence4(ys), Duration.Inf)) // Throws exception
package ed
// To demonstrate different ways of pattern matching, let's consider the
// following example case class and instance:
case class Person(name: String, age: Int)
......@@ -5,20 +7,20 @@ val ada = Person("Ada", 36)
// Run using `sbt "runMain ed.patternMatching"`.
@main def patternMatching =
// There are several ways to pattern match on the `ada` instance:
// 1. Pattern matching on the case class constructor using `Person(name, age)`.
// If the pattern matches, the value of `n` is bound `ada.name` field, and
// `a` is bound to the `ada.age` field.
ada match
case Person(n, a) => println(f"$n is $a years old")
// There are several ways to pattern match on the `ada` instance:
// 1. Pattern matching on the case class constructor using `Person(name, age)`.
// If the pattern matches, the value of `n` is bound `ada.name` field, and
// `a` is bound to the `ada.age` field.
ada match
case Person(n, a) => println(f"$n is $a years old")
// 2. We can also check only that `ada` is of type `Person` without binding its
// fields by using a `:` pattern. The `:` pattern is used to check if a value is
// of a certain type.
ada match
case p: Person => println(f"${p.name} is ${p.age} years old")
// 2. We can also check only that `ada` is of type `Person` without binding its
// fields by using a `:` pattern. The `:` pattern is used to check if a value is
// of a certain type.
ada match
case p: Person => println(f"${p.name} is ${p.age} years old")
// 3. If we want to both bind the fields and bind a value to the whole instance,
// we can use an `@` pattern.
ada match
case p @ Person(n, a) => println(f"${p.name} is ${a} years old")
// 3. If we want to both bind the fields and bind a value to the whole instance,
// we can use an `@` pattern.
ada match
case p @ Person(n, a) => println(f"${p.name} is ${a} years old")
package ed
import concurrent.{Await, Future}
import concurrent.duration.Duration
import concurrent.ExecutionContext.Implicits.global
// Pro Tip: you can print the code generated by the Scala compiler after the
// translation of for-comprehensions by running:
//
// scala -Xprint:firstTransform src/main/scala/ed/40056.scala
@main def futuresForTranslation =
val f1 = Future { 451 }
val f2 = Future { 1984 }
val f3 = for v1 <- f1; v2 <- f2 yield v1 + v2
println(Await.result(f3, Duration.Inf))
val f4 = Future { 451 }
val f5 = Future { 1984 }
val f6 = f4.flatMap(v4 => f5.map(v5 => v4 + v5))
println(Await.result(f6, Duration.Inf))
......@@ -9,10 +9,10 @@ import akka.pattern.pipe
import akka.testkit.{ImplicitSender, TestKit}
import akka.util.Timeout
/**
* This demonstrates a simplified implementation of the ask pattern.
/** This demonstrates a simplified implementation of the ask pattern.
*
* @param promise the promise to be completed when a message is received
* @param promise
* the promise to be completed when a message is received
*/
class AskMiniActor(promise: Promise[Any]) extends Actor:
def receive = LoggingReceive {
......@@ -21,16 +21,18 @@ class AskMiniActor(promise: Promise[Any]) extends Actor:
extension (receiver: ActorRef)
def ?(msg: Any)(using
// In this simplified implementation, we don't use the timeout.
timeout: Timeout,
// This only used for logging purposes (and to get the actor system in the
// real implementation), but is not used otherwise in the implementation.
sender: ActorRef,
// In the real implementation, the actor system is retrieved differently,
// but here we pass it as an additional implicit parameter for simplicity.
context: ActorContext
// In this simplified implementation, we don't use the timeout.
timeout: Timeout,
// This only used for logging purposes (and to get the actor system in the
// real implementation), but is not used otherwise in the implementation.
sender: ActorRef,
// In the real implementation, the actor system is retrieved differently,
// but here we pass it as an additional implicit parameter for simplicity.
context: ActorContext
): Future[Any] =
context.system.log.debug(s"Create mini actor to ask $msg from $sender to $receiver")
context.system.log.debug(
s"Create mini actor to ask $msg from $sender to $receiver"
)
// Create a `Promise` that will be completed when a message is received.
val promise = Promise[Any]()
......@@ -49,91 +51,93 @@ extension (receiver: ActorRef)
promise.future
object Person:
enum Protocol:
case GetName
case GetAge
enum Response:
case Name(name: String)
case Age(age: Int)
case UnknownMessage
enum Protocol:
case GetName
case GetAge
enum Response:
case Name(name: String)
case Age(age: Int)
case UnknownMessage
class Person(name: String, age: Int) extends Actor:
import Person.Protocol._
import Person.Response._
import Person.Protocol.*
import Person.Response.*
def receive = LoggingReceive {
case GetName => sender() ! Name(name)
case GetAge => sender() ! Age(age)
case _ => sender() ! UnknownMessage
case GetAge => sender() ! Age(age)
case _ => sender() ! UnknownMessage
}
object PersonsDatabase:
enum Protocol:
case CreatePerson(name: String, age: Int)
case GetPersonNames(ids: List[Int])
enum Response:
case PersonCreated(id: Int)
case PersonNames(names: List[String])
enum Protocol:
case CreatePerson(name: String, age: Int)
case GetPersonNames(ids: List[Int])
enum Response:
case PersonCreated(id: Int)
case PersonNames(names: List[String])
class PersonsDatabase extends Actor:
import Person.Protocol._
import Person.Response._
import PersonsDatabase.Protocol._
import PersonsDatabase.Response._
var persons: Map[Int, ActorRef] = Map.empty
var maxId = 0
given ExecutionContext = context.system.dispatcher
given Timeout = Timeout(200.millis)
def receive = LoggingReceive {
case CreatePerson(name, age) =>
val personRef = context.actorOf(Props(Person(name, age)))
persons = persons + (maxId -> personRef)
sender() ! PersonCreated(maxId)
maxId += 1
case GetPersonNames(ids) =>
// We ask every person for their name using the ask pattern. This
// gives us a list of `Future`s that will each be completed with a
// `Name` message.
val rawResponsesFutures: List[Future[Any]] = ids.map(id => (persons(id) ? GetName))
// We first map each `Future[Any]` to a `Future[Name]` using the
// `mapTo` method. Then, we map each `Future[Name]` to a
// `Future[String]` using the `map` method.
val namesFutures: List[Future[String]] = rawResponsesFutures.map(_.mapTo[Name].map(_.name))
// We use the `Future.sequence` method to convert a
// `List[Future[String]]` to a `Future[List[String]]`. The resulting
// future will be completed once all the futures in the input list
// are completed.
val futureOfNames: Future[List[String]] = Future.sequence(namesFutures)
// Finally, map the `Future[List[String]]` to a
// `Future[PersonNames]` and pipe it to the sender of the
// `GetPersonNames` message.
futureOfNames.map(PersonNames.apply).pipeTo(sender())
}
@main def askPatternDemo() = new TestKit(ActorSystem("DebugSystem")) with ImplicitSender:
import Person.Protocol._
import Person.Response._
import PersonsDatabase.Protocol._
import PersonsDatabase.Response._
try
val personsDb = system.actorOf(Props(PersonsDatabase()))
personsDb ! CreatePerson("Anita", 20)
expectMsg(PersonCreated(0))
personsDb ! CreatePerson("Berta", 30)
expectMsg(PersonCreated(1))
personsDb ! CreatePerson("Cecilia", 40)
expectMsg(PersonCreated(2))
personsDb ! GetPersonNames(List(0, 1, 2))
expectMsg(PersonNames(List("Anita", "Berta", "Cecilia")))
finally shutdown(system)
import Person.Protocol.*
import Person.Response.*
import PersonsDatabase.Protocol.*
import PersonsDatabase.Response.*
var persons: Map[Int, ActorRef] = Map.empty
var maxId = 0
given ExecutionContext = context.system.dispatcher
given Timeout = Timeout(200.millis)
def receive = LoggingReceive {
case CreatePerson(name, age) =>
val personRef = context.actorOf(Props(Person(name, age)))
persons = persons + (maxId -> personRef)
sender() ! PersonCreated(maxId)
maxId += 1
case GetPersonNames(ids) =>
// We ask every person for their name using the ask pattern. This
// gives us a list of `Future`s that will each be completed with a
// `Name` message.
val rawResponsesFutures: List[Future[Any]] =
ids.map(id => (persons(id) ? GetName))
// We first map each `Future[Any]` to a `Future[Name]` using the
// `mapTo` method. Then, we map each `Future[Name]` to a
// `Future[String]` using the `map` method.
val namesFutures: List[Future[String]] =
rawResponsesFutures.map(_.mapTo[Name].map(_.name))
// We use the `Future.sequence` method to convert a
// `List[Future[String]]` to a `Future[List[String]]`. The resulting
// future will be completed once all the futures in the input list
// are completed.
val futureOfNames: Future[List[String]] = Future.sequence(namesFutures)
// Finally, map the `Future[List[String]]` to a `Future[PersonNames]` and
// pipe it to the sender of the `GetPersonNames` message.
futureOfNames.map(PersonNames.apply).pipeTo(sender())
}
@main def askPatternDemo() =
new TestKit(ActorSystem("DebugSystem")) with ImplicitSender:
import Person.Protocol.*
import Person.Response.*
import PersonsDatabase.Protocol.*
import PersonsDatabase.Response.*
try
val personsDb = system.actorOf(Props(PersonsDatabase()))
personsDb ! CreatePerson("Anita", 20)
expectMsg(PersonCreated(0))
personsDb ! CreatePerson("Berta", 30)
expectMsg(PersonCreated(1))
personsDb ! CreatePerson("Cecilia", 40)
expectMsg(PersonCreated(2))
personsDb ! GetPersonNames(List(0, 1, 2))
expectMsg(PersonNames(List("Anita", "Berta", "Cecilia")))
finally shutdown(system)
package lecture13
import akka.actor.{Actor, ActorContext, ActorRef, ActorSystem, Props}
import akka.event.LoggingReceive
import akka.testkit.{ImplicitSender, TestKit}
class CounterRight extends Actor:
def counter(n: Int): Receive = {
case "increment" => context.become(counter(n + 1), discardOld = false)
// ^^^^^^^^^^^^^^^^^^
// This is needed.
case "get" => sender() ! n
case "decrement" => if n != 0 then context.unbecome()
}
def receive = counter(0)
class CounterWrong extends Actor:
def counter(n: Int): Receive = {
case "increment" => context.become(counter(n + 1))
case "get" => sender() ! n
case "decrement" => if n != 0 then context.unbecome()
}
def receive = counter(0)
@main def becomeDemo() =
new TestKit(ActorSystem("DebugSystem")) with ImplicitSender:
try
val counter = system.actorOf(Props(CounterRight()), "counter")
counter ! "increment"
counter ! "get"
expectMsg(1)
counter ! "increment"
counter ! "get"
expectMsg(2)
counter ! "decrement"
counter ! "get"
expectMsg(1)
// This is wrong if we use CounterWrong because it doesn't set the
// discardOld parameter to false. Therefore, it will not remember the
// previous state and reset the behavior to the initial one, which is
// `counter(0)`.
counter ! "decrement"
counter ! "get"
expectMsg(0)
finally shutdown(system)
......@@ -16,13 +16,30 @@ def sequentialConvolve(
from: Int,
until: Int
): Unit = {
// Approach A, as described in the clarification announcement for this
// exercise, where we treat `from` and `until` as indices on `output`
// instead of `input` as in the given code.
var iOutput = from
while iOutput < until do
var iKernel = math.max(0, iOutput - input.length + 1)
while iKernel < kernel.length && iKernel <= iOutput do
// `output` is only ever written to between the indices `from` and
// `until`, the range of `iOutput`. The indices for `input` and
// `kernel` are computed accordingly.
output(iOutput) += input(iOutput - iKernel) * kernel(iKernel)
iKernel += 1
iOutput += 1
// ALTERNATE SOLUTION: Approach B, as described in the clarification
// announcement for this exercise, which is unchanged from the given
// code, i.e. we treat `from` and `until` as indices on `input`.
var iInput = from
while iInput < until do
var iKernel = 0
while iKernel < kernel.length do
output(iInput + iKernel) += input(iInput) * kernel(iKernel)
iKernel += 1
iInput += 1
}
def parallelConvolve(
......@@ -32,7 +49,12 @@ def parallelConvolve(
from: Int,
until: Int,
threshold: Int
): Unit =
): Unit =
// Approach A, as described in the clarification announcement for this
// exercise, where we treat `from` and `until` as indices on `output`
// instead of `input` as in the given code. This does not require us to
// change anything in this function. Only receives full credit if used
// together with Approach A for `sequentialConvolve`.
if (until - from) <= threshold then
sequentialConvolve(input, kernel, output, from, until)
else
......@@ -42,6 +64,31 @@ def parallelConvolve(
parallelConvolve(input, kernel, output, mid, until, threshold)
)
// ALTERNATE SOLUTION: Approach B, as described in the clarification
// announcement for this exercise, where we treat `from` and `until` as
// indices on `input` as in the given code. This requires up to leave a
// gap in the parallel calls to be filled in sequentially as described
// below. Only receives full credit if used together with Approach B for
// `sequentialConvolve`.
if (until - from) <= threshold then
sequentialConvolve(input, kernel, output, from, until)
else
val mid = from + (until - from) / 2
val gap = numContributeTo(input, kernel, output, mid)
// Leave a gap large enough that accesses to `output` from the first
// parallel call will not overlap with accesses from the second. This
// gap is of size equal to the number of elements of `input` that will
// contribute to the computation of the result at the lowest index of
// `output` in the second parallel call, namely, at index `mid`.
// However, we are careful not to access indices lower than `from`.
val gapBeforeMid = Math.max(from, mid - gap + 1)
parallel(
parallelConvolve(input, kernel, output, from, gapBeforeMid, threshold),
parallelConvolve(input, kernel, output, mid, until, threshold)
)
// Fill in the gap sequentially.
sequentialConvolve(input, kernel, output, gapBeforeMid, mid)
object Original_WithOutputRace:
def sequentialConvolve(
......@@ -50,15 +97,13 @@ object Original_WithOutputRace:
output: Array[Int],
from: Int,
until: Int): Unit =
var iOutput = from
while iOutput < until do
var iInput = from
while iInput < until do
var iKernel = 0
while iKernel < kernel.length do
val iInput = iOutput - iKernel
if 0 <= iInput && iInput < input.length then
output(iOutput) += input(iInput) * kernel(iKernel)
output(iInput + iKernel) += input(iInput) * kernel(iKernel)
iKernel += 1
iOutput += 1
iInput += 1
def parallelConvolve(
input: Array[Int],
......