diff --git a/assignment4/ass4.pdf b/assignment4/ass4.pdf new file mode 100644 index 0000000..b777fcd Binary files /dev/null and b/assignment4/ass4.pdf differ diff --git a/assignment4/main.oz b/assignment4/main.oz new file mode 100644 index 0000000..969643a --- /dev/null +++ b/assignment4/main.oz @@ -0,0 +1,35 @@ +declare Enumerate GenerateOdd ListDivisorsOf ListPrimesUntil EnumerateLazy Primes in + +fun {Enumerate Start End} Inner in + fun {Inner Start End} + if End == Start then + Start|nil + else + Start|{Inner (Start+1) End} + end + end + thread {Inner Start End} end +end + +fun {GenerateOdd Start End} + thread {Filter {Enumerate Start End} fun {$ N} N mod 2 \= 0 end} end +end + +fun {ListDivisorsOf Number} + thread {Filter {Enumerate 1 Number} fun {$ N} Number mod N == 0 end} end +end + +fun {ListPrimesUntil Number} + thread {Filter {Enumerate 2 Number} fun {$ N} {List.length {ListDivisorsOf N}} == 2 end} end +end + +fun lazy {EnumerateLazy} + % stolen from + % https://github.com/alhassy/OzCheatSheet?tab=readme-ov-file#lazy-evaluation + fun lazy {Ints N} N|{Ints N+1} end + in {Ints 1} +end + +fun lazy {Primes} + {Filter {EnumerateLazy} fun {$ N} {List.length {ListDivisorsOf N}} == 2 end} +end diff --git a/assignment4/report.md b/assignment4/report.md new file mode 100644 index 0000000..7fb0243 --- /dev/null +++ b/assignment4/report.md @@ -0,0 +1,204 @@ +--- +author: fredrik robertsen +date: 2025-10-27 +title: "assignment 4" +--- + +## task 1 + +### (a) + +the code quite consistently outputs + +``` +30 +3000 +10 +20 +200 +100 +``` + +### (b) + +the result is as such, because 30 gets printed first, since statements are +sequential. then 3000 gets printed, which is the last statement. then, all +main statements have been executed, so the threads will start executing, +starting with the first thread to have been declared. thus it outputs 10. but +then while the thread is waiting for 100 ms, the next thread gets to run and 20 +is printed. then the last thread finishes first and prints 200, then lastly 100 +from the first thread. + +it is indeed possible for it to output a different sequence. i managed to have +it print out 100 before 200, which is what i would've expected it to print out +initially, since the first thread would have been waiting while the last thread +got to printing, but for some reason most cases show that the last thread prints +before the first. + +### (c) + +the code consistently outputs + +``` +2 +20 +22 +``` + +### (d) + +the code initially comes to `C = A + B` and halts, letting threads execute. the +first thread sets `A = 2` and prints this, then the second thread sets +`B = A * 10` such that 20 is printed. then lastly we print 22. + +this code will always produce the correct values (no data races), but it may +rarely print a different order. this is because C waits for A and B, and B waits +for A. thus A has to be handled first, then B, then C. however, since the second +thread isn't atomic, it may be slow to show, thus the main thread might +print first and we'd end up with + +``` +2 +22 +20 +``` + +## task 2 + +the following code is my implementation of `{Enumerate Start End}`, which should +asynchronously generate a stream of numbers between both endpoints, inclusive. + +```oz +fun {Enumerate Start End} Inner in + fun {Inner Start End} + if End == Start then + Start|nil + else + Start|{Inner (Start+1) End} + end + end + thread {Inner Start End} end +end +``` + +using the previous function, i can simply filter this list on modulo 2 to see if +it is divisible by 2 or not, i.e. if it is odd or even. + +```oz +fun {GenerateOdd Start End} + thread + {Filter + {Enumerate Start End} + fun {$ N} + N mod 2 \= 0 + end + } + end +end +``` + +displaying these functions using `Show` yields `_` as an output, due +to my wrapping of the output in a `thread ... end` block. + +using `Browse` correctly shows the expected numbers. + +my understanding is that if the function returns a thread that performs some +computation that doesn't spawn more threads (which is why we use an `Inner` +function in `Enumerate`), then it will perform that computation asynchronously +whenever that function is called. + +thus, if we use `Browse`, it will perform the computation and display the output +in the `Browse`-window as soon as it is completed. but `Show` will immediately +attempt to print out the value, which doesn't give the cpu any time to perform +the computation, thus there are no values to show yet, and it is only shown as +the mysterious `_`. + +## task 3 + +we can similarly to `GenerateOdd` consume `Enumerate` in various ways to obtain +different lists of numbers. this is actually one of the main modes of +computation in array programming, where programs are often compositions of +functions that ultimately act on an array of numbers, such as that of an +iota-sequence, which is the same as `{Enumerate 0 N-1}` or `{Enumerate 1 N}`, +depending on indexing. + +anyway, we can find the divisors of a number by checking for divisibility +(remainder/modulus is 0) of each number up to that number. this can be done as +follows + +```oz +fun {ListDivisorsOf Number} + thread + {Filter + {Enumerate 1 Number} + fun {$ N} + Number mod N == 0 + end + } + end +end +``` + +we can then simply count how many elements are in the list of divisors of +a given number to find if it is prime or not. a prime number has only itself and +1 as divisors, so the length of the list must be 2. the following code filters +numbers satisfying this. + +```oz +fun {ListPrimesUntil Number} + thread + {Filter + {Enumerate 2 Number} + fun {$ N} + {List.length {ListDivisorsOf N}} == 2 + end + } + end +end +``` + +note that we start our enumerate on 2, since we already know 1 isn't prime. we +could make further optimizations, such as avoiding the expensive +`ListDivisorsOf` call for even numbers greater than 2, essentially halving our +computational load. but i digress + +# task 4 + +the following code defines a lazy infinite list counting from 1 and up in +increments of 1. + +i couldn't find many examples of code using the `lazy` keyword, but i found one, +which was precisely a counting example. so i could just use that as a base to +fix the argument to 1. + +```oz +fun lazy {Enumerate} + % stolen from + % https://github.com/alhassy/OzCheatSheet?tab=readme-ov-file#lazy-evaluation + fun lazy {Ints N} N|{Ints N+1} end + in {Ints 1} +end +``` + +the `Ints` function simply builds an infinite recursion on itself, but since it +is specified as lazy, oz knows to only compute the next step when it needs to. + +we can filter this infinite list to obtain an infinite list of prime numbers. we +use the same logic to check for primality, i.e. counting the number of divisors +of the given number. + +```oz +fun lazy {Primes} + {Filter {EnumerateLazy} fun {$ N} {List.length {ListDivisorsOf N}} == 2 end} +end +``` + +this code produces a list that contains all prime numbers. i.e. + +```oz +{Browse {Primes}.1} % -> 2 +{Browse {Primes}.2.1} % -> 3 +{Browse {Primes}.2.2.1} % -> 5 +{Browse {Primes}.2.2.2.1} % -> 7 +... +``` diff --git a/assignment4/report.pdf b/assignment4/report.pdf new file mode 100644 index 0000000..e8b32ba Binary files /dev/null and b/assignment4/report.pdf differ