diff --git a/scala_project_2025/part1/report.md b/scala_project_2025/part1/report.md new file mode 100644 index 0000000..20d7fda --- /dev/null +++ b/scala_project_2025/part1/report.md @@ -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()) + } + + ... +} +``` diff --git a/scala_project_2025/part1/report.pdf b/scala_project_2025/part1/report.pdf new file mode 100644 index 0000000..66c2906 Binary files /dev/null and b/scala_project_2025/part1/report.pdf differ