scp1: report
This commit is contained in:
192
scala_project_2025/part1/report.md
Normal file
192
scala_project_2025/part1/report.md
Normal file
@@ -0,0 +1,192 @@
|
||||
---
|
||||
date: 2025-10-14
|
||||
author: fredrik robertsen
|
||||
title: scala project part 1
|
||||
---
|
||||
|
||||
## task 1
|
||||
|
||||
### a)
|
||||
|
||||
to create a range from 1 through 50 explicitly with a for-loop, we can
|
||||
initialize a mutable collection and update it.
|
||||
|
||||
```scala
|
||||
// immutable pointer
|
||||
val values = new collection.mutable.ArrayBuffer[Int]
|
||||
for (i <- 1 to 50)
|
||||
values.append(i)
|
||||
```
|
||||
|
||||
### b)
|
||||
|
||||
we can sum numbers in an integer array `Array[Int]` by iterating over the
|
||||
numbers and updating a mutable variable `result`.
|
||||
|
||||
```scala
|
||||
def iterative_sum(numbers: Array[Int]): Int =
|
||||
var result = 0
|
||||
for (num <- numbers)
|
||||
result += num
|
||||
return result
|
||||
```
|
||||
|
||||
note: the `return` keyword can be omitted, which will be done in later tasks.
|
||||
|
||||
### c)
|
||||
|
||||
as usual, we can use an auxiliary function to carry the accumulated value of the
|
||||
recursion, thus yielding a tail-recursive sum. the compiler may or may not
|
||||
optimize tail-recursive functions in scala. we can force it to do so using the
|
||||
`@tailrec` annotation on our recursive auxiliary function `inner`.
|
||||
|
||||
```scala
|
||||
def recursive_sum(numbers: Array[Int]): Int =
|
||||
@tailrec def inner(numbers: Array[Int], accumulator: Int): Int =
|
||||
if numbers.length <= 0 then accumulator
|
||||
else inner(numbers.tail, numbers.head + accumulator)
|
||||
inner(numbers, 0)
|
||||
```
|
||||
|
||||
### d)
|
||||
|
||||
we can use the classic definition for fibonacci numbers to obtain a simple
|
||||
recursive function. the main point here is that we make the base-case
|
||||
a `BigInt`, such that subsequent arithmetic conforms to this type, thus
|
||||
satisfying the specified return-type.
|
||||
|
||||
```scala
|
||||
def fibonacci(n: Int): BigInt =
|
||||
if n <= 1 then BigInt(1)
|
||||
else fibonacci(n - 1) + fibonacci(n - 2)
|
||||
```
|
||||
|
||||
note: the aforementioned `return` statements have been omitted.
|
||||
|
||||
## task 2
|
||||
|
||||
### a-b)
|
||||
|
||||
i reimplemented my oz-functions in scala. the first function finds the roots of
|
||||
a second degree polynomial optionally if there are any real solutions. the main
|
||||
difference is how we communicate to the caller that there were no real
|
||||
solutions.
|
||||
|
||||
in oz we use the named return variable `RealSol` for the `proc`ess
|
||||
`QuadraticEquation`.
|
||||
|
||||
```oz
|
||||
proc {QuadraticEquation A B C ?RealSol ?X1 ?X2} Discriminant in
|
||||
Discriminant = B*B - 4.0*A*C
|
||||
RealSol = Discriminant >= 0.0
|
||||
if RealSol then
|
||||
X1 = (~B - {Sqrt Discriminant})/(2.0*A)
|
||||
X2 = (~B + {Sqrt Discriminant})/(2.0*A)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
in scala we use an `Option` that is either `Some(x)` or `None`. we encode the
|
||||
two solutions as a tuple. note: if there is only one unique solution, both
|
||||
values in the tuple will be equal.
|
||||
|
||||
```scala
|
||||
def quadratic(a: Double, b: Double, c: Double): Option[(Double, Double)] =
|
||||
val disc = math.pow(b, 2) - 4 * a * c
|
||||
if disc < 0 then None
|
||||
else Some((-b - math.sqrt(disc)) / (2 * a), (-b + math.sqrt(disc)) / (2 * a))
|
||||
```
|
||||
|
||||
the last function encodes a polynomial as a sort of curried function that takes
|
||||
in the coefficients and returns a function that evaluates the given polynomial
|
||||
at that point.
|
||||
|
||||
```oz
|
||||
fun {Quadratic A B C}
|
||||
fun {$ X} A*X*X + B*X + C end
|
||||
end
|
||||
```
|
||||
|
||||
```scala
|
||||
def polynomial(a: Double, b: Double, c: Double): Double => Double =
|
||||
(x: Double) => a * x * x + b * x + c
|
||||
```
|
||||
|
||||
the solutions for anonymous function return values are vastly similar between
|
||||
the languages.
|
||||
|
||||
## task 3
|
||||
|
||||
### a)
|
||||
|
||||
we can instantiate a `Thread` object and override its `run` method with our own
|
||||
`execute` function. note: the input function `execute` is similar to a void
|
||||
function in C and denotes some effectual action that likely modifies some state.
|
||||
we must take care to avoid race conditions when initializing multiple such
|
||||
threads.
|
||||
|
||||
```scala
|
||||
def initialize_thread(execute: () => Unit): Thread =
|
||||
new Thread { override def run = execute() }
|
||||
```
|
||||
|
||||
### b)
|
||||
|
||||
the code runs endlessly and is supposed to have one value increment, while the
|
||||
other decrements, as if moving one unit from one stack to another, maintaining
|
||||
the same number of total such units. we start with 1000 units in one stack and
|
||||
0 in the other.
|
||||
|
||||
however, in the course of the program, the scheduler on my computer lets the
|
||||
threads we create to perform this task of moving units run in a weird order,
|
||||
thus ending up increasing the total amount of units we have available through
|
||||
a race condition.
|
||||
|
||||
this is a classic asynchronous programming pitfall which would be devastating
|
||||
for applications like a bank, where the units would be money, and such
|
||||
concurrent transactions would yield an unintentional net increase or decrease
|
||||
in someone's balance.
|
||||
|
||||
this could very much happen in oz as well. however, oz is made with concurrency
|
||||
in mind, making it harder to make such a mistake.
|
||||
|
||||
### c)
|
||||
|
||||
the easiest (least-effort) way to modify the code such that it is correct and
|
||||
thread-safe is to lock the critical section using scala's `synchronized` method
|
||||
|
||||
```scala
|
||||
def execute(): Unit = {
|
||||
while (true) {
|
||||
this.synchronized { // force serialization
|
||||
moveOneUnit()
|
||||
updateSum()
|
||||
}
|
||||
Thread.sleep(50)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
another way to make it safe is to lock the internal values before updating them,
|
||||
for example using atomic data types such as `AtomicInteger`s
|
||||
|
||||
```scala
|
||||
object ConcurrencyTroubles {
|
||||
private var value1: AtomicInteger = new AtomicInteger(1000)
|
||||
private var value2: AtomicInteger = new AtomicInteger(0)
|
||||
private var sum: AtomicInteger = new AtomicInteger(0)
|
||||
|
||||
def moveOneUnit(): Unit = {
|
||||
value1.decrementAndGet()
|
||||
value2.incrementAndGet()
|
||||
value1.compareAndSet(0, 1000)
|
||||
value2.compareAndSet(1000, 0)
|
||||
}
|
||||
|
||||
def updateSum(): Unit = {
|
||||
sum.setRelease(value1.get() + value2.get())
|
||||
}
|
||||
|
||||
...
|
||||
}
|
||||
```
|
||||
BIN
scala_project_2025/part1/report.pdf
Normal file
BIN
scala_project_2025/part1/report.pdf
Normal file
Binary file not shown.
Reference in New Issue
Block a user