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
Showing
with 966 additions and 0 deletions
package concpar21final03
import instrumentation.*
/** A synchronization mechanism allowing multiple reads to proceed concurrently
* with an update to the state.
*/
class RCU extends Monitor:
protected val latestVersion: AtomicLong = AtomicLong(0)
protected val readersVersion: ThreadMap[Long] = ThreadMap()
/** This method must be called before accessing shared data for reading. */
def startRead(): Unit =
assert(
!readersVersion.currentThreadHasValue,
"startRead() cannot be called multiple times without an intervening stopRead()"
)
readersVersion.setCurrentThreadValue(latestVersion.get)
/** Once a thread which has previously called `startRead` has finished reading
* shared data, it must call this method.
*/
def stopRead(): Unit =
assert(
readersVersion.currentThreadHasValue,
"stopRead() cannot be called without a preceding startRead()"
)
readersVersion.deleteCurrentThreadValue()
/** Wait until all reads started before this method was called have finished,
* then return.
*/
def waitForOldReads(): Unit =
val newVersion = latestVersion.incrementAndGet()
readersVersion.waitForall(_ >= newVersion)
package concpar21final03
import instrumentation.*
import scala.collection.mutable
/** A map which associates every thread to at most one value of type A.
*
* Every method in this class is thread-safe.
*/
class ThreadMap[A] extends Monitor:
protected val theMap: mutable.Map[Thread, A] = mutable.Map()
/** Return the value in the map entry for the current thread if it exists,
* otherwise None.
*/
def currentThreadValue: Option[A] = synchronized {
theMap.get(Thread.currentThread)
}
/** Is there a map entry for the current thread? */
def currentThreadHasValue: Boolean =
synchronized {
theMap.contains(Thread.currentThread)
}
/** Set the map entry of the current thread to `value` and notify any thread
* waiting on `waitForall`.
*/
def setCurrentThreadValue(value: A): Unit =
synchronized {
theMap(Thread.currentThread) = value
notifyAll()
}
/** Delete the map entry associated with this thread (if it exists) and notify
* all threads waiting in `waitForall`.
*/
def deleteCurrentThreadValue(): Unit =
synchronized {
theMap.remove(Thread.currentThread)
notifyAll()
}
/** Wait until `predicate` returns true for all map entries, then return. */
def waitForall(predicate: A => Boolean): Unit =
synchronized {
while !theMap.forall((_, value) => predicate(value)) do wait()
}
end ThreadMap
package concpar21final03
import instrumentation.*
class UpdateServer(fs: FileSystem) extends Monitor:
val rcu = new RCU
/** The name of the file containing the latest update.
*
* This is `@volatile` to guarantee that `fetchUpdate` always sees the latest
* filename.
*/
@volatile private var updateFile: Option[FileName] = None
/** Return the content of the latest update if one is available, otherwise
* None.
*
* This method is thread-safe.
*/
def fetchUpdate(): Option[String] =
// TODO: use `rcu`
rcu.startRead()
val value = updateFile.map(fs.readFile)
rcu.stopRead()
value
/** Define a new update, more precisely this will:
* - Create a new update file called `newName` with content `newContent`
* - Ensure that any future call to `fetchUpdate` returns the new update
* content.
* - Delete the old update file.
*
* This method is _NOT_ thread-safe, it cannot be safely called from multiple
* threads at once.
*/
def newUpdate(newName: FileName, newContent: String): Unit =
// TODO: use `rcu`
val oldFile = updateFile
fs.createFile(newName, newContent)
updateFile = Some(newName)
rcu.waitForOldReads()
oldFile.foreach(fs.deleteFile)
end UpdateServer
package concpar21final03.instrumentation
/** A long value that may be updated atomically. */
class AtomicLong(initial: Long):
private val atomic = new java.util.concurrent.atomic.AtomicLong(initial)
/** Get the current value. */
def get: Long = atomic.get()
/** Set to the given `value`. */
def set(value: Long): Unit = atomic.set(value)
/** Atomically increment by one the current value and return the _original_
* value.
*/
def getAndIncrement(): Long =
atomic.getAndIncrement()
/** Atomically increment by one the current value and return the _updated_
* value.
*/
def incrementAndGet(): Long =
atomic.incrementAndGet()
/** Atomically set the value to `newValue` if the current value == `expected`.
*
* Return true if successful, otherwise return false to indicate that the
* actual value was not equal to the expected value.
*/
def compareAndSet(expected: Long, newValue: Long): Boolean =
atomic.compareAndSet(expected, newValue)
end AtomicLong
package concpar21final03.instrumentation
class Dummy
trait Monitor:
implicit val dummy: Dummy = new Dummy
def wait()(implicit i: Dummy) = waitDefault()
def synchronized[T](e: => T)(implicit i: Dummy) = synchronizedDefault(e)
def notify()(implicit i: Dummy) = notifyDefault()
def notifyAll()(implicit i: Dummy) = notifyAllDefault()
private val lock = new AnyRef
// Can be overridden.
def waitDefault(): Unit = lock.wait()
def synchronizedDefault[T](toExecute: => T): T = lock.synchronized(toExecute)
def notifyDefault(): Unit = lock.notify()
def notifyAllDefault(): Unit = lock.notifyAll()
package concpar22final01
trait Problem1 extends Lib:
class DLLCombinerImplementation extends DLLCombiner:
// Copies every other Integer element of data array, starting from the first (index 0), up to the middle
def task1(data: Array[Int]) = task {
var current = first
var i = 0
while current != null && i < size / 2 do
data(i) = current.value
i += 2
current = current.getNext2
}
// Copies every other Integer element of data array, starting from the second, up to the middle
def task2(data: Array[Int]) = task {
var current = second
var i = 1
while current != null && i < size / 2 do
data(i) = current.value
i += 2
current = current.getNext2
}
// Copies every other Integer element of data array, starting from the second to last, up to the middle
def task3(data: Array[Int]) = task {
var current = secondToLast
var i = size - 2
while current != null && i >= size / 2 do
data(i) = current.value
i -= 2
current = current.getPrevious2
}
// Copies every other Integer element of data array, starting from the last, up to the middle
// This is executed on the current thread.
def task4(data: Array[Int]) =
var current = last
var i = size - 1
while current != null && i >= size / 2 do
data(i) = current.value
i -= 2
current = current.getPrevious2
def result(): Array[Int] =
val data = new Array[Int](size)
val t1 = task1(data)
val t2 = task2(data)
val t3 = task3(data)
task4(data)
t1.join()
t2.join()
t3.join()
data
package concpar22final01
import java.util.concurrent.*
import scala.util.DynamicVariable
trait Lib:
class Node(val value: Int):
protected var next: Node = null // null for last node.
protected var next2: Node = null // null for last node.
protected var previous: Node = null // null for first node.
protected var previous2: Node = null // null for first node.
def getNext: Node = next // do NOT use in the result method
def getNext2: Node = next2
def getPrevious: Node = previous // do NOT use in the result method
def getPrevious2: Node = previous2
def setNext(n: Node): Unit = next = n
def setNext2(n: Node): Unit = next2 = n
def setPrevious(n: Node): Unit = previous = n
def setPrevious2(n: Node): Unit = previous2 = n
// Simplified Combiner interface
// Implements methods += and combine
// Abstract methods should be implemented in subclasses
abstract class DLLCombiner:
var first: Node = null // null for empty lists.
var last: Node = null // null for empty lists.
var second: Node = null // null for empty lists.
var secondToLast: Node = null // null for empty lists.
var size: Int = 0
// Adds an Integer to this array combiner.
def +=(elem: Int): Unit =
val node = new Node(elem)
if size == 0 then
first = node
last = node
size = 1
else
last.setNext(node)
node.setPrevious(last)
node.setPrevious2(last.getPrevious)
if size > 1 then last.getPrevious.setNext2(node)
else second = node
secondToLast = last
last = node
size += 1
// Combines this array combiner and another given combiner in constant O(1) complexity.
def combine(that: DLLCombiner): DLLCombiner =
if this.size == 0 then that
else if that.size == 0 then this
else
this.last.setNext(that.first)
this.last.setNext2(that.first.getNext)
if this.last.getPrevious != null then
this.last.getPrevious.setNext2(that.first) // important
that.first.setPrevious(this.last)
that.first.setPrevious2(this.last.getPrevious)
if that.first.getNext != null then
that.first.getNext.setPrevious2(this.last) // important
if this.size == 1 then second = that.first
this.size = this.size + that.size
this.last = that.last
this.secondToLast = that.secondToLast
this
def task1(data: Array[Int]): ForkJoinTask[Unit]
def task2(data: Array[Int]): ForkJoinTask[Unit]
def task3(data: Array[Int]): ForkJoinTask[Unit]
def task4(data: Array[Int]): Unit
def result(): Array[Int]
def task[T](body: => T): ForkJoinTask[T]
package concpar22final02
import instrumentation.Monitor
abstract class AbstractBarrier(val numThreads: Int) extends Monitor:
var count = numThreads
def awaitZero(): Unit
def countDown(): Unit
package concpar22final02
class Barrier(numThreads: Int) extends AbstractBarrier(numThreads):
def awaitZero(): Unit =
synchronized {
while count > 0 do wait()
}
def countDown(): Unit =
synchronized {
count -= 1
if count <= 0 then notifyAll()
}
package concpar22final02
import scala.collection.mutable.ArrayBuffer
class ImageLib(size: Int):
val buffer1: ArrayBuffer[ArrayBuffer[Int]] = ArrayBuffer.fill(size, size)(1)
val buffer2: ArrayBuffer[ArrayBuffer[Int]] = ArrayBuffer.fill(size, size)(0)
enum Filter(val kernel: Array[Array[Int]]):
case Outline extends Filter(Array(
Array(-1, -1, -1),
Array(-1, 8, -1),
Array(-1, -1, -1)
))
case Sharpen extends Filter(Array(
Array(0, -1, 0),
Array(-1, 5, -1),
Array(0, -1, 0)
))
case Emboss
extends Filter(Array(Array(-2, -1, 0), Array(-1, 1, 1), Array(0, 1, 2)))
case Identity
extends Filter(Array(Array(0, 0, 0), Array(0, 1, 0), Array(0, 0, 0)))
def init(input: ArrayBuffer[ArrayBuffer[Int]]) =
for i <- 0 to size - 1 do
for j <- 0 to size - 1 do
buffer1(i)(j) = input(i)(j)
def computeConvolution(
kernel: Array[Array[Int]],
input: ArrayBuffer[ArrayBuffer[Int]],
row: Int,
column: Int
): Int =
val displacement = Array(-1, 0, 1)
var output = 0
for i <- 0 to 2 do
for j <- 0 to 2 do
val newI = row + displacement(i)
val newJ = column + displacement(j)
if newI < 0 || newI >= size || newJ < 0 || newJ >= size then output += 0
else output += (kernel(i)(j) * input(newI)(newJ))
output
def applyFilter(
kernel: Array[Array[Int]],
input: ArrayBuffer[ArrayBuffer[Int]],
output: ArrayBuffer[ArrayBuffer[Int]],
row: Int
): Unit =
for i <- 0 to input(row).size - 1 do
output(row)(i) = computeConvolution(kernel, input, row, i)
package concpar22final02
import java.util.concurrent.atomic.AtomicInteger
import scala.collection.mutable.ArrayBuffer
class Problem2(imageSize: Int, numThreads: Int, numFilters: Int):
val barrier: ArrayBuffer[Barrier] =
ArrayBuffer.fill(numFilters)(Barrier(numThreads))
val imageLib: ImageLib = ImageLib(imageSize)
def imagePipeline(
filters: Array[imageLib.Filter],
rows: Array[Int]
): ArrayBuffer[ArrayBuffer[Int]] =
for i <- 0 to filters.size - 1 do
for j <- 0 to rows.size - 1 do
if i % 2 == 0 then
imageLib.applyFilter(
filters(i).kernel,
imageLib.buffer1,
imageLib.buffer2,
rows(j)
)
else
imageLib.applyFilter(
filters(i).kernel,
imageLib.buffer2,
imageLib.buffer1,
rows(j)
)
barrier(i).countDown()
barrier(i).awaitZero()
if filters.size % 2 == 0 then imageLib.buffer1
else imageLib.buffer2
package concpar22final02.instrumentation
class Dummy
trait Monitor:
implicit val dummy: Dummy = new Dummy
def wait()(implicit i: Dummy) = waitDefault()
def synchronized[T](e: => T)(implicit i: Dummy) = synchronizedDefault(e)
def notify()(implicit i: Dummy) = notifyDefault()
def notifyAll()(implicit i: Dummy) = notifyAllDefault()
private val lock = new AnyRef
// Can be overriden.
def waitDefault(): Unit = lock.wait()
def synchronizedDefault[T](toExecute: => T): T = lock.synchronized(toExecute)
def notifyDefault(): Unit = lock.notify()
def notifyAllDefault(): Unit = lock.notifyAll()
trait LockFreeMonitor extends Monitor:
override def waitDefault() =
throw new Exception("Please use lock-free structures and do not use wait()")
override def synchronizedDefault[T](toExecute: => T): T =
throw new Exception(
"Please use lock-free structures and do not use synchronized()"
)
override def notifyDefault() =
throw new Exception(
"Please use lock-free structures and do not use notify()"
)
override def notifyAllDefault() =
throw new Exception(
"Please use lock-free structures and do not use notifyAll()"
)
/* Copyright 2009-2015 EPFL, Lausanne */
package concpar22final02.instrumentation
import java.lang.management.*
/** A collection of methods that can be used to collect run-time statistics */
object Stats:
def timed[T](code: => T)(cont: Long => Unit): T =
var t1 = System.currentTimeMillis()
val r = code
cont((System.currentTimeMillis() - t1))
r
def withTime[T](code: => T): (T, Long) =
var t1 = System.currentTimeMillis()
val r = code
(r, (System.currentTimeMillis() - t1))
package concpar22final03
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.util.Random
trait Economics:
/** A trading card from the game Scala: The Programming. We can own a card,
* but once don't anymore.
*/
final class Card(val name: String)
def isMine(c: Card): Boolean
/** This function uses the best available database to return the sell value of
* a card on the market.
*/
def valueOf(cardName: String): Int = List(1, cardName.length).max
/** This method represents an exact amount of money that can be hold, spent,
* or put in the bank
*/
final class MoneyBag()
def moneyIn(m: MoneyBag): Int
/** If you sell a card, at some point in the future you will get some money
* (in a bag).
*/
def sellCard(c: Card): Future[MoneyBag]
/** You can buy any "Scala: The Programming" card by providing a bag of money
* with the appropriate amount and waiting for the transaction to take place.
* You will own the returned card.
*/
def buyCard(money: MoneyBag, name: String): Future[Card]
/** This simple bank account holds money for you. You can bring a money bag to
* increase your account's balance, or withdraw a money bag of any size not
* greater than your account's balance.
*/
def balance: Int
def withdraw(amount: Int): Future[MoneyBag]
def deposit(bag: MoneyBag): Future[Unit]
class NotEnoughMoneyException
extends Exception("Not enough money provided to buy those cards")
package concpar22final03
import scala.concurrent.Future
import concurrent.ExecutionContext.Implicits.global
trait Problem3:
val economics: Economics
import economics.*
/** The objective is to propose a service of deck building. People come to you
* with some money and some cards they want to sell, and you need to return
* them a complete deck of the cards they want.
*/
def orderDeck(
bag: MoneyBag,
cardsToSell: List[Card],
wantedDeck: List[String]
): Future[List[Card]] =
Future {
val totalGivenMoney =
cardsToSell.foldLeft(moneyIn(bag))((sum, c) => sum + valueOf(c.name))
val totalNeededMoney =
wantedDeck.foldLeft(0)((sum, n) => sum + valueOf(n))
if totalGivenMoney < totalNeededMoney then
throw new NotEnoughMoneyException()
val soldCards: Future[Unit] =
if moneyIn(bag) != 0 then
sellListOfCards(cardsToSell).zip(deposit(bag)).map(_ => ())
else sellListOfCards(cardsToSell).map(_ => ())
soldCards.flatMap { _ => buyListOfCards(wantedDeck) }
}.flatten
/** This helper function will sell the provided list of cards and put the
* money on your personal bank account. It returns a Future of Unit, which
* indicates when all sales are completed.
*/
def sellListOfCards(cardsToSell: List[Card]): Future[Unit] =
val moneyFromSales: List[Future[Unit]] = cardsToSell.map { c =>
sellCard(c).flatMap(m => deposit(m).map { _ => })
}
Future
.sequence(moneyFromSales)
.map(_ =>
()
) // Future.sequence transforms a List[Future[A]] into a Future[List[A]]
/** This helper function, given a list of wanted card names and assuming there
* is enough money in the bank account, will buy (in the future) those cards,
* and return them.
*/
def buyListOfCards(wantedDeck: List[String]): Future[List[Card]] =
val boughtCards: List[Future[Card]] = wantedDeck.map { name =>
withdraw(valueOf(name)).flatMap(mb => buyCard(mb, name))
}
Future.sequence(boughtCards)
package concpar22final04
import akka.actor.*
import akka.testkit.*
import java.util.Date
import akka.event.LoggingReceive
import akka.pattern.*
import akka.util.Timeout
import concurrent.duration.*
import scala.concurrent.Future
import scala.concurrent.ExecutionContext
given timeout: Timeout = Timeout(200.millis)
/** Data associated with a song: a unique `id`, a `title` and an `artist`.
*/
case class Song(id: Int, title: String, artist: String)
/** An activity in a user's activity feed, representing that `userRef` is
* listening to `songId`.
*/
case class Activity(userId: String, userName: String, songId: Int)
/** Companion object of the `User` class.
*/
object User:
/** Messages that can be sent to User actors.
*/
enum Protocol:
/** Asks for a user name and id. Should be answered by a Response.Info.
*/
case GetInfo
/** Asks home page data. Should be answered by a Response.HomepageData.
*/
case GetHomepageData
/** Like song with id `songId`.
*/
case Like(songId: Int)
/** Unlike song with id `songId`.
*/
case Unlike(songId: Int)
/** Adds `subscriber` to the list of subscribers.
*/
case Subscribe(subscriber: ActorRef)
/** Remove `subscriber` from the list of subscribers.
*/
case Unsubscribe(subscriber: ActorRef)
/** Adds the activity `activity` to the activity feed. This message will be
* sent by the users this user has subscribed to.
*/
case AddActivity(activity: Activity)
/** Sent when a user starts playing a song with id `songId`. The recipient
* should notify all its subscribers to update their activity feeds by
* sending them `AddActivity(Activity(...))` messages. No answer is
* expected. This message is sent by external actors.
*/
case Play(songId: Int)
/** Asks for home page text. Should be answered by a Response.HomepageText.
*/
case GetHomepageText
/** Responses that can be sent back from User actors.
*/
enum Responses:
/** Answer to a Protocol.GetInfo message
*/
case Info(id: String, name: String)
/** Answer to a Protocol.GetHomepageData message
*/
case HomepageData(songIds: List[Int], activities: List[Activity])
/** Answer to a Protocol.GetHomepageText message
*/
case HomepageText(result: String)
/** The `User` actor, responsible to handle `User.Protocol` messages.
*/
class User(id: String, name: String, songsStore: ActorRef) extends Actor:
import User.*
import User.Protocol.*
import User.Responses.*
import SongsStore.Protocol.*
import SongsStore.Responses.*
given ExecutionContext = context.system.dispatcher
/** Liked songs, by reverse date of liking time (the last liked song must be
* the first must be the first element of the list). Elements of this list
* must be unique: a song can only be liked once. Liking a song twice should
* not change the order.
*/
var likedSongs: List[Int] = List()
/** Users who have subscribed to this users.
*/
var subscribers: Set[ActorRef] = Set()
/** Activity feed, by reverse date of activity time (the last added activity
* must be the first element of the list). Items in this list should be
* unique by `userRef`. If a new activity with a `userRef` already in the
* list is added, the former should be removed, so that we always see the
* latest activity for each user we have subscribed to.
*/
var activityFeed: List[Activity] = List()
/** This actor's behavior. */
override def receive: Receive = LoggingReceive {
case GetInfo =>
sender() ! Info(id, name)
case GetHomepageData =>
sender() ! HomepageData(likedSongs, activityFeed)
case Like(songId) if !likedSongs.contains(songId) =>
likedSongs = songId :: likedSongs
case Unlike(songId) =>
likedSongs = likedSongs.filter(_ != songId)
case Subscribe(ref: ActorRef) =>
subscribers = subscribers + ref
case Unsubscribe(ref: ActorRef) =>
subscribers = subscribers - ref
case AddActivity(activity: Activity) =>
activityFeed =
activity :: activityFeed.filter(_.userId != activity.userId)
case Play(songId) =>
subscribers.foreach(_ ! AddActivity(Activity(id, name, songId)))
case GetHomepageText =>
val likedSongsFuture: Future[Songs] =
(songsStore ? GetSongs(likedSongs)).mapTo[Songs]
val activitySongsFuture: Future[Songs] =
(songsStore ? GetSongs(activityFeed.map(_.songId))).mapTo[Songs]
val response: Future[HomepageText] =
for
likedSongs <- likedSongsFuture;
activitySongs <- activitySongsFuture
yield HomepageText(
f"""
|Howdy ${name}!
|
|Liked Songs:
|${likedSongs.songs
.map(song => f"* ${song.title} by ${song.artist}")
.mkString("\n")}
|
|Activity Feed:
|${activityFeed
.zip(activitySongs.songs)
.map((activity, song) =>
f"* ${activity.userName} is listening to ${song.title} by ${song.artist}"
)
.mkString("\n")}""".stripMargin.trim
)
response.pipeTo(sender())
}
/** Objects containing the messages a songs store should handle.
*/
object SongsStore:
/** Ask information about a list of songs by their ids.
*/
enum Protocol:
case GetSongs(songIds: List[Int])
/** List of `Song` corresponding to the list of IDs given to `GetSongs`.
*/
enum Responses:
case Songs(songs: List[Song])
/** A mock implementation of a songs store.
*/
class MockSongsStore extends Actor:
import SongsStore.Protocol.*
import SongsStore.Responses.*
import SongsStore.*
val songsDB = Map(
1 -> Song(1, "High Hopes", "Pink Floyd"),
2 -> Song(2, "Sunny", "Boney M."),
3 -> Song(3, "J'irai où tu iras", "Céline Dion & Jean-Jacques Goldman"),
4 -> Song(4, "Ce monde est cruel", "Vald"),
5 -> Song(5, "Strobe", "deadmau5"),
6 -> Song(6, "Désenchantée", "Mylène Farmer"),
7 -> Song(7, "Straight Edge", "Minor Threat"),
8 -> Song(8, "Hold the line", "TOTO"),
9 -> Song(9, "Anarchy in the UK", "Sex Pistols"),
10 -> Song(10, "Breakfast in America", "Supertramp")
)
override def receive: Receive = LoggingReceive { case GetSongs(songsIds) =>
sender() ! Songs(songsIds.map(songsDB))
}
/////////////////////////
// DEBUG //
/////////////////////////
/** Infrastructure to help debugging. In sbt use `run` to execute this code. The
* TestKit is an actor that can send messages and check the messages it
* receives (or not).
*/
@main def debug() = new TestKit(ActorSystem("DebugSystem")) with ImplicitSender:
import User.*
import User.Protocol.*
import User.Responses.*
import SongsStore.Protocol.*
import SongsStore.Responses.*
try
val songsStore = system.actorOf(Props(MockSongsStore()), "songsStore")
val anita = system.actorOf(Props(User("100", "Anita", songsStore)))
anita ! Like(6)
expectNoMessage() // expects no message is received
anita ! GetHomepageData
expectMsg(HomepageData(List(6), List()))
finally shutdown(system)
// This demonstrates how to import functions from a package inside a worksheet.
import midterm22.{task, find}
find(Array(1, 2, 3), 2, 1)
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)
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")
// 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")
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))
import java.util.concurrent.atomic.AtomicInteger
import collection.parallel.CollectionConverters.*
// This demo should help understanding the `add` method implementation in
// src/main/scala/midterm22/Part8.scala.
// TL;DR: it demonstrate that `+=` is not atomic.
var id: Int = 0
val l =
for _ <- (0 until 10).par yield
id += 1
id
l
l.distinct.size
// Try to execute the worksheet several times (by saving again and again). We
// might get a number of distinct elements not equal to 10. Why?
//
// First, note that `id += 1` is equivalent to `val tmp = id; id = tmp + 1`.
// I.e. the read and write are two separate operations; the `+=` operation is
// not *atomic*.
//
// Then, consider the following execution:
// - T1: reads 0 from id and store it in its local `tmp` var.
// - T2: does the same: reads 0 from id and store it in its local `tmp` var.
// - T1: computes `tmp + 1 == 1`, stores it in `id` and returns `1`
// - T2: computes `tmp + 1 == 1`, stores it in `id` and returns `1`
//
// Now we have two times the ID 1!
// The code we wrote before is equivalent to:
var id2: Int = 0
val l2 =
for _ <- (0 until 10).par yield
val tmp = id2
id2 = tmp + 1
val tmp2 = id2
tmp2
l2
l2.distinct.size // *
// How to make it correct? Use an atomic integer!
val id3 = AtomicInteger()
val l3 =
for _ <- (0 until 10).par yield id3.incrementAndGet()
l3
// Now we are sure that this will always be 10.
l3.distinct.size // *
assert(l3.distinct.size == 10)
// Or, using only what has been seen before the midterm this year: another
// solution is to use synchronize:
var id4: Int = 0
val lock = Object()
val l4 =
for _ <- (0 until 10).par yield lock.synchronized {
id4 += 1
id4
}
l4
// Now we are sure that this will always be 10.
l4.distinct.size // *
assert(l4.distinct.size == 10)