From c31d5ff44983fd6b51979d39277df6e3c784dcb2 Mon Sep 17 00:00:00 2001 From: Samuel Neo <24467645+samuelneo@users.noreply.github.com> Date: Sun, 21 Apr 2024 01:37:00 +0800 Subject: [PATCH 1/5] add provided files --- PA/PA2/2320/AbstractStateM.java | 17 +++++++++++ PA/PA2/2320/Pair.java | 54 +++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 PA/PA2/2320/AbstractStateM.java create mode 100644 PA/PA2/2320/Pair.java diff --git a/PA/PA2/2320/AbstractStateM.java b/PA/PA2/2320/AbstractStateM.java new file mode 100644 index 0000000..d6d174b --- /dev/null +++ b/PA/PA2/2320/AbstractStateM.java @@ -0,0 +1,17 @@ +import java.util.function.Function; + +abstract class AbstractStateM { + private final Function> f; + + AbstractStateM(T t) { + this.f = s -> new Pair(t, s); + } + + AbstractStateM(Function> f) { + this.f = f; + } + + Pair accept(S s) { + return this.f.apply(s); + } +} diff --git a/PA/PA2/2320/Pair.java b/PA/PA2/2320/Pair.java new file mode 100644 index 0000000..ae8018a --- /dev/null +++ b/PA/PA2/2320/Pair.java @@ -0,0 +1,54 @@ +/** + * This utility class stores two items together in a pair. + * It could be used, for instance, to faciliate returning of + * two values in a function. + * + * @author cs2030 + * @param the type of the first element + * @param the type of the second element + **/ +public class Pair { + private final T t; + private final U u; + + /** + * Creates a {@code Pair} of items. + * + * @param t first item of the pair + * @param u second item of the pair + **/ + public Pair(T t, U u) { + this.t = t; + this.u = u; + } + + /** + * Returns the first item of the pair. + * + * @return the first item of the pair + */ + public T first() { + return this.t; + } + + /** + * Returns the second item of the pair. + * + * @return the second item of the pair + */ + public U second() { + return this.u; + } + + /** + * Returns a string representation of this pair enclosed in ({@code "()"}). + * The two elements are separated by the characters {@code ", "} (comma and space). + * Elements are converted to strings as by {@link String#valueOf(Object)}. + * + * @return a string representation of this list + */ + @Override + public String toString() { + return "(" + this.t + ", " + this.u + ")"; + } +} From cd46467a73b0bd10cc53d7fc190ff9fa4845dbbc Mon Sep 17 00:00:00 2001 From: Samuel Neo <24467645+samuelneo@users.noreply.github.com> Date: Sun, 21 Apr 2024 01:42:16 +0800 Subject: [PATCH 2/5] add html --- PA/PA2/2320/PA2_2320.html | 539 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 539 insertions(+) create mode 100644 PA/PA2/2320/PA2_2320.html diff --git a/PA/PA2/2320/PA2_2320.html b/PA/PA2/2320/PA2_2320.html new file mode 100644 index 0000000..e479078 --- /dev/null +++ b/PA/PA2/2320/PA2_2320.html @@ -0,0 +1,539 @@ +

Throughout this course, we have been working with and encouraged to use pure functions with no side-effects, where possible. + +

+    jshell> Function<String, Integer> f = s -> s.length()
+    f ==> $Lambda$..
+    
+    jshell> Function<Integer, Integer> g = x -> x * 2
+    g ==> $Lambda$..
+    
+    jshell> f.apply("abc")
+    $.. ==> 3
+    
+    jshell> g.apply(3)
+    $.. ==> 6
+    
+ +

Pure functions can also be composed to create more complex pure functions: + +

+    jshell> g.compose(f).apply("abc")
+    $.. ==> 6
+    
+ +

Let us consider logging using an external variable (or state). + +

+    jshell> String log = ""
+    log ==> ""
+    
+    jshell> Function<String, Integer> f = s -> { log = log + "f"; return s.length(); }
+    f ==> $Lambda$..
+    
+    jshell> Function<Integer, Integer> g = x -> { log = log + "g"; return x * 2; }
+    g ==> $Lambda$..
+    
+    jshell> g.compose(f).apply("abc")
+    $.. ==> 6
+    
+    jshell> log
+    log ==> "fg" // log is changed!
+    
+    jshell> g.compose(f).apply("abc")
+    $.. ==> 6
+    
+    jshell> log
+    log ==> "fgfg" // log is changed again!
+    
+ +

One way of avoiding the side-effect is to encapsulate the value and the log into a pair. + +

+    jshell> Function<Pair<String, String>, Pair<Integer, String>> f = x -> 
+       ...> new Pair<Integer, String>(x.first().length(), x.second() + "f")
+    f ==> $Lambda$..
+    
+    jshell> Function<Pair<Integer, String>, Pair<Integer, String>> g = x -> 
+       ...> new Pair<Integer, String>(x.first() * 2, x.second() + "g")
+    g ==> $Lambda$..
+    
+    jshell> g.compose(f).apply(new Pair<String, String>("abc",""))
+    $.. ==> (6, fg)
+    
+    jshell> g.compose(f).apply(new Pair<String, String>("abc",""))
+    $.. ==> (6, fg) // value is unchanged
+    
+ +

However, the above entails that the accumulation of the log is to be handled together with the transformation of the value by each pure function, such as f and g. + +

We shall now attempt to separate these two tasks. + Let us first generalize our situation by considering the two functions f and g below with arbitrary types + A, + B, and + C. + +

    +
  • f :: A -> B +
  • g :: B -> C +
+ + which can be composed to give g o f :: A -> C + +

As we have seen, we can include the state (of type S) by adding it to a pair. + +

    +
  • f :: (A, S) -> (B, S) +
  • g :: (B, S) -> (C, S) +
+ +

which will give the composition g o f :: (A, S) -> (C, S). + +

Notice that the functions f and g above can be curried: + +

    +
  • f :: A -> (S -> (B, S)) +
  • g :: B -> (S -> (C, S)) +
+ +

but we cannot readily compose g o f as the output type of f and input type of g are now different. + +

Let us encapsulate the function of the form (S -> (T, S)) into a context StateM<T, S>. + We now have + +

    +
  • f :: A -> StateM<B, S> +
  • g :: B -> StateM<C, S> +
+ + begin with an initial context state of type StateM<A, + S>. To perform the composition, we can now make use of + flatMap! + +
+    state.flatMap(f).flatMap(g)
+    
+ +

Task

+ +

Your task is to define StateM with the appropriate flatMap method, as well as other methods to update the state. + +

Take Note!

+ +

This task comprises a number of levels. + You are required to complete ALL levels.

+ +

The following are the constraints imposed on this task. + In general, you should keep to the constructs and programming discipline + instilled throughout the module.

+ +
    +
  • Write each class or interface in its own file. + Do not use single letter names for classes or interfaces. +
  • +
  • Ensure that ALL object properties and class constants are declared private final, unless otherwise specified.
  • +
  • Ensure that your classes are NOT cyclic dependent.
  • +
  • ONLY the following java libraries ARE allowed: +
      +
    • java.util.Optional +
    • functional interfaces from java.util.function +
    +
  • The following are NOT allowed: +
      +
    • null +
    • instanceof + + +
    • for, while +
    • enum +
    • Optional methods: isPresent, isEmpty, get and its variants (orElse, orElseGet, orElseThrow), as well as equals + +
    +
  • There is no need to use bounded wildcards. +
  • You are NOT allowed to define anonymous inner classes; define lambdas instead. +
  • Other usual restrictions: +
      +
    • Use only &&, || and ! in logical expressions. + DO NOT use bitwise operators. +
    • You are NOT allowed to use * wildcard imports. +
    • You are NOT allowed to use method references :: +
    • You are NOT allowed to define array constructs, e.g. + String[] or using ellipsis, e.g.String... +
    • You are NOT allowed to use Java reflection, i.e. Object::getClasses and other methods from java.lang.Class +
    +
+ +

The + Pair and + AbstractStateM + classes have been provided for you. + You are NOT ALLOWED to modify these classes. + +

Level 1

+ +

Given the following AbstractStateM class. + +

+    abstract class AbstractStateM<T, S> {
+        private final Function<S, Pair<T, S>> f;
+    
+        AbstractStateM(Function<S, Pair<T, S>> f) {
+            this.f = f;
+        }
+    
+        AbstractStateM(T t) {
+            this(s -> new Pair<T, S>(t, s));
+        }
+    
+        Pair<T, S> accept(S s) {
+            return this.f.apply(s);
+        }
+    }
+    
+ +

Write the concrete class StateM<T, S> that inherits from AbstractStateM with the following methods: + +

    +
  • a static factory method unit that takes in a value of type T and returns a StateM object with the value wrapped within it; +
  • a toString method that returns "StateM" as the string representation. +
+ +

You may write your own constructors. However, DO NOT declare any + other instance properties or constants in StateM. + All other helper methods, if any, must be declared private. + +

+    jshell> StateM.<String, Integer>unit("init")
+    $.. ==> StateM
+    
+ +

The initial state value of type S is passed + into the pipeline via the terminating accept method. + Subsequently, a Pair<T, S> object comprising the value + of type T and the state of type S is returned at + the end of the pipeline. + +

+    jshell> StateM.<String, Integer>unit("init").accept(0)
+    $.. ==> (init, 0)
+    
+ +

Level 2

+ +

To change the state within the pipeline, we need two more static factory methods to read and write states. + +

Write the get method that takes no arguments, and returns a StateM object where the value is the same as the state. + +

+    jshell> StateM.<Integer>get()
+    $.. ==> StateM
+    
+    jshell> StateM.<Integer>get().accept(0)
+    $.. ==> (0, 0)
+    
+    jshell> StateM.<Integer>get().accept(10)
+    $.. ==> (10, 10)
+    
+ +

Next, write the put method that takes in a new state and returns + StateM with the updated state, but no value (or nothing). + +

You will first need to define a Nothing type (or class) according to the sample run below: + +

+    jshell> Nothing nothing = Nothing.nothing()
+    nothing ==> -
+    
+    jshell> Function<Nothing, Integer> f = x -> 1
+    f ==> $Lambda$..
+    
+    jshell> f.apply(Nothing.nothing())
+    $.. ==> 1
+    
+    jshell> StateM.<Integer>put(10)
+    $.. ==> StateM
+    
+    jshell> StateM.<Integer>put(10).accept(0)
+    $.. ==> (-, 10)
+    
+ + The last test case above first takes in 0 as the state, + which then gets updated to 10. + +

It is worth noting that since unit, get and + put are static factory methods, they cannot be called one + after another, e.g. + +

+    jshell> StateM.<String, Integer>unit("init").get().put(10).unit(4).accept(0)
+    $.. ==> (4, 0)
+    
+ +

is just simply + +

+    jshell> StateM.<Integer, Integer>unit(4).accept(0)
+    $.. ==> (4, 0)
+    
+ +

Moreover, chaining in the above way does not allow updating the state to say, "10 more than the previous state". This is where flatMap comes in. + + +

Level 3

+ +

Write the flatMap method to sequence StateM objects following the sample run below. + You can ignore bounded wildcards for simplicity. + +

+    jshell> StateM.<String, Integer>unit("init").
+       ...> flatMap(x -> StateM.<Integer, Integer>unit(x.length()))
+    $.. ==> StateM
+    
+    jshell> StateM.<String, Integer>unit("init").
+       ...> flatMap(x -> StateM.<Integer, Integer>unit(x.length())).accept(0)
+    $.. ==> (4, 0)
+    
+    jshell> Function<Integer, StateM<Integer, String>> id = x -> 
+       ...> StateM.<Integer, String>unit(x)
+    id ==> $Lambda$..
+    
+    jshell> Function<Integer, StateM<Integer, String>> f = x -> 
+       ...> StateM.<Integer, String>unit(x + 1)
+    f ==> $Lambda$..
+    
+    jshell> f.apply(1).accept("initState")
+    $.. ==> (2, initState)
+    
+    jshell> id.apply(1).flatMap(f).accept("initState")
+    $.. ==> (2, initState)
+    
+    jshell> f.apply(1).flatMap(id).accept("initState")
+    $.. ==> (2, initState)
+    
+    jshell> StateM.<Integer, String>unit(1).
+       ...> flatMap(f).
+       ...> flatMap(f).
+       ...> accept("initState")
+    $.. ==> (3, initState)
+    
+    jshell> StateM.<Integer, String>unit(1).
+       ...> flatMap(x -> f.apply(x).
+       ...> flatMap(f)).accept("initState")
+    $.. ==> (3, initState)
+    
+ +

A more practical test case is given below. + +

+    jshell> StateM<Integer, Integer> bar(StateM<String, Integer> sm) {
+       ...>     return sm.flatMap(x -> StateM.<Integer>get()
+       ...>         .flatMap(y -> StateM.<Integer>put(y + 10))
+       ...>         .flatMap(z -> StateM.<Integer, Integer>unit(x.length())));
+       ...> }
+    |  modified method bar(StateM<String, Integer>)
+    
+    jshell> bar(StateM.<String, Integer>unit("init")).accept(1)
+    $.. ==> (4, 11)
+    
+ +

Admittedly the bar method looks rather contrived. + However, its monad comprehension is just simply + +

+    StateM bar(StateM<String, Integer> sm) {
+       do {
+          x <- sm; // get the string value from sm
+          y <- get(); // get the state from sm
+          put(y + 10); // increase state by 10
+          unit(x.length()); // transform string value to its length
+       }
+    }
+    
+ +

Interestingly, here is how we can make use of a String state for logging: + +

+    jshell> Function<String, StateM<Integer, String>> f = s -> {
+       ...>     return StateM.<String>get()
+       ...>         .flatMap(x -> StateM.<String>put(x + "f"))
+       ...>         .flatMap(y -> StateM.<Integer, String>unit(s.length()));
+       ...> }
+    f ==> $Lambda$..
+    
+    jshell> Function<Integer, StateM<Integer, String>> g = x -> {
+       ...>     return StateM.<String>get()
+       ...>         .flatMap(y -> StateM.<String>put(y + "g"))
+       ...>         .flatMap(z -> StateM.<Integer, String>unit(2 * x));
+       ...> }
+    g ==> $Lambda$..
+    
+    jshell> StateM.<String, String>unit("abc").flatMap(f).flatMap(g).accept("")
+    $.. ==> (6, fg)
+    
+ + + +

Level 4

+ +

The following is an attempt to count the number of method calls to the fib method when finding the n-th term of the Fibonacci sequence. + Clearly, it uses an external state with side-effects. + +

+    jshell> int count = 0
+    count ==> 0
+    
+    jshell> int fib(int n) { 
+       ...>     count = count + 1;
+       ...>     if (n <= 1) {
+       ...>         return n;
+       ...>     } else {
+       ...>         return fib(n - 1) + fib(n - 2);
+       ...>     }
+       ...> }
+    |  modified method fib(int)
+    
+    jshell> fib(5)
+    $.. ==> 5
+    
+    jshell> count
+    count ==> 15
+    
+    jshell> fib(5)
+    $.. ==> 5
+    
+    jshell> count
+    count ==> 30
+    
+ +

Rewrite the fib function in level4.jsh so as to capture the number of function activations using StateM<Integer, Integer> instead. + +

First, create a method inc() in level4.jsh to increment the integer state by 1. + +

+    shell> inc()
+    $.. ==> StateM
+    
+    jshell> inc().accept(0)
+    $.. ==> (-, 1)
+    
+    jshell> inc().accept(10)
+    $.. ==> (-, 11)
+    
+ +

Now use the following monad comprehension as a guide to define the fib method. + +

+    StateM<Integer, Integer> fib(int n) {
+       do {
+          inc();
+          if (n <= 1) {
+             unit(n);
+          } else {
+             do {
+                x <- fib(x-1);
+                y <- fib(x-2);
+                unit(x+y)
+             }
+          }
+       }
+    }
+    
+ +

A sample run is given below. + +

+    jshell> fib(5)
+    $.. ==> StateM
+    
+    jshell> fib(5).accept(0)
+    $.. ==> (5, 15)
+    
+    jshell> fib(6).accept(0)
+    $.. ==> (8, 25)
+    
+ +

Level 5

+ +

Finally, we would like to find out the number of function activations and the maximum depth of recursion for the Ackermann function defined below: + +

+    jshell> int ack(int m, int n) {
+       ...>     if (m == 0) {
+       ...>         return n + 1;
+       ...>     }
+       ...>     if (n == 0) {
+       ...>         return ack(m - 1, 1);
+       ...>     }
+       ...>     return ack(m - 1, ack(m, n - 1));
+       ...> }
+    |  created method ack(int,int)
+    
+    jshell> ack(1, 2)
+    $.. ==> 4
+    
+ +

Rewrite the ack function in level5.jsh so that it returns the result, together with the number of method calls and maximum depth of recursion. + +

Define your own custom state in the class FuncStat. + +

+    jshell> ack(1, 2)
+    $.. ==> StateM
+    
+    jshell> new FuncStat()
+    $.. ==> count=0 maxDepth=0
+    
+    jshell> Pair<Integer, FuncStat> result = ack(1, 2).accept(new FuncStat())
+    result ==> (4, [count=6 maxDepth=4]) // 6 function calls with maximum recursion depth of 4
+    
+ + Hint: Try to define the monad comprehension first, then apply the translation scheme. \ No newline at end of file From 3afc6ebc350a9dbb0db5e453a793d69b20a8aa86 Mon Sep 17 00:00:00 2001 From: Samuel Neo <24467645+samuelneo@users.noreply.github.com> Date: Sun, 21 Apr 2024 01:44:47 +0800 Subject: [PATCH 3/5] update html --- PA/PA2/2320/PA2_2320.html | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/PA/PA2/2320/PA2_2320.html b/PA/PA2/2320/PA2_2320.html index e479078..f088676 100644 --- a/PA/PA2/2320/PA2_2320.html +++ b/PA/PA2/2320/PA2_2320.html @@ -1,3 +1,6 @@ +

CS2030 Practical Assessment #2

+

Back to homepage

+

Throughout this course, we have been working with and encouraged to use pure functions with no side-effects, where possible.

@@ -164,8 +167,8 @@ 

Take Note!

The - Pair and - AbstractStateM + Pair and + AbstractStateM classes have been provided for you. You are NOT ALLOWED to modify these classes. From c374932fbe649d237795da8def5eab8918f7da3e Mon Sep 17 00:00:00 2001 From: Samuel Neo <24467645+samuelneo@users.noreply.github.com> Date: Sun, 21 Apr 2024 01:55:48 +0800 Subject: [PATCH 4/5] add test .jsh files --- PA/PA2/2320/test1.jsh | 2 ++ PA/PA2/2320/test2.jsh | 10 ++++++++++ PA/PA2/2320/test3.jsh | 20 ++++++++++++++++++++ PA/PA2/2320/test4.jsh | 6 ++++++ PA/PA2/2320/test5.jsh | 10 ++++++++++ 5 files changed, 48 insertions(+) create mode 100644 PA/PA2/2320/test1.jsh create mode 100644 PA/PA2/2320/test2.jsh create mode 100644 PA/PA2/2320/test3.jsh create mode 100644 PA/PA2/2320/test4.jsh create mode 100644 PA/PA2/2320/test5.jsh diff --git a/PA/PA2/2320/test1.jsh b/PA/PA2/2320/test1.jsh new file mode 100644 index 0000000..234a6f6 --- /dev/null +++ b/PA/PA2/2320/test1.jsh @@ -0,0 +1,2 @@ +StateM.unit("init") +StateM.unit("init").accept(0) \ No newline at end of file diff --git a/PA/PA2/2320/test2.jsh b/PA/PA2/2320/test2.jsh new file mode 100644 index 0000000..025016f --- /dev/null +++ b/PA/PA2/2320/test2.jsh @@ -0,0 +1,10 @@ +StateM.get() +StateM.get().accept(0) +StateM.get().accept(10) +Nothing nothing = Nothing.nothing() +Function f = x -> 1 +f.apply(Nothing.nothing()) +StateM.put(10) +StateM.put(10).accept(0) +StateM.unit("init").get().put(10).unit(4).accept(0) +StateM.unit(4).accept(0) \ No newline at end of file diff --git a/PA/PA2/2320/test3.jsh b/PA/PA2/2320/test3.jsh new file mode 100644 index 0000000..ebd0013 --- /dev/null +++ b/PA/PA2/2320/test3.jsh @@ -0,0 +1,20 @@ +StateM.unit("init").flatMap(x -> StateM.unit(x.length())) +StateM.unit("init").flatMap(x -> StateM.unit(x.length())).accept(0) +Function> id = x -> StateM.unit(x) +Function> f = x -> StateM.unit(x + 1) +f.apply(1).accept("initState") +id.apply(1).flatMap(f).accept("initState") +f.apply(1).flatMap(id).accept("initState") +StateM.unit(1).flatMap(f).flatMap(f).accept("initState") +StateM.unit(1).flatMap(x -> f.apply(x).flatMap(f)).accept("initState") +StateM bar(StateM sm) { + return sm.flatMap(x -> StateM.get().flatMap(y -> StateM.put(y + 10)).flatMap(z -> StateM.unit(x.length()))); +} +bar(StateM.unit("init")).accept(1) +Function> f = s -> { + return StateM.get().flatMap(x -> StateM.put(x + "f")).flatMap(y -> StateM.unit(s.length())); +} +Function> g = x -> { + return StateM.get().flatMap(y -> StateM.put(y + "g")).flatMap(z -> StateM.unit(2 * x)); +} +StateM.unit("abc").flatMap(f).flatMap(g).accept("") \ No newline at end of file diff --git a/PA/PA2/2320/test4.jsh b/PA/PA2/2320/test4.jsh new file mode 100644 index 0000000..1404d76 --- /dev/null +++ b/PA/PA2/2320/test4.jsh @@ -0,0 +1,6 @@ +inc() +inc().accept(0) +inc().accept(10) +fib(5) +fib(5).accept(0) +fib(6).accept(0) \ No newline at end of file diff --git a/PA/PA2/2320/test5.jsh b/PA/PA2/2320/test5.jsh new file mode 100644 index 0000000..ed46944 --- /dev/null +++ b/PA/PA2/2320/test5.jsh @@ -0,0 +1,10 @@ +ack(1, 2) +new FuncStat() +Pair result = ack(1, 2).accept(new FuncStat()) +System.out.println("--- extra test cases added by the contributor :D ---") +System.out.println("ack(1, 0): expected - (2, count=2 maxDepth=2), output - ") +Pair result = ack(1, 0).accept(new FuncStat()) +System.out.println("ack(4, 0): expected - (13, count=107 maxDepth=16), output - ") +Pair result = ack(4, 0).accept(new FuncStat()) +System.out.println("ack(3, 2): expected - (29, count=541 maxDepth=31), output - ") +Pair result = ack(3, 2).accept(new FuncStat()) \ No newline at end of file From 25a2a8226a8ebcbf5eb42a1e459857592f0a4e23 Mon Sep 17 00:00:00 2001 From: Samuel Neo <24467645+samuelneo@users.noreply.github.com> Date: Sun, 21 Apr 2024 02:01:48 +0800 Subject: [PATCH 5/5] update index.html --- index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/index.html b/index.html index 068265f..e88753f 100644 --- a/index.html +++ b/index.html @@ -33,6 +33,7 @@

PA2

  • AY21/22 Semester 2: AbstractNum
  • AY22/23 Semester 1: Expression
  • AY22/23 Semester 2: Divide and Conquer
  • +
  • AY23/24 Semester 2: StateM
  • Past Finals