Ex 6: The Art of Being Lazy
Basic Information
- Deadline: 22 October 2024, Tuesday, 23:59 SGT
- Difficulty: ★★★★★★
Prerequisite
- Caught up to Unit 32 of Lecture Notes.
- Familiar with CS2030S Java style guide.
Class Files
If you have not finished Programming Exercise 5, do not worry.
We provide .class
files for the functional interfaces as well as Maybe<T>
.
Note that the implementation for Maybe<T>
is badly written not following OOP but it is the correct implementation.
Additionally, the class files were compiled on PE node using Java 21 compiled. If you are not using Java 21 or if you are not working on PE node, you may get different result. It is unlikely, but the possibility is there. Please only do your work on the PE node.
Note that since we provide only the .class
files for Maybe<T>
and the functional interfaces, you may need to compile Lazy<T>
with the following command from ex6-username
directory.
1 |
|
Maybe
Our Maybe
class has the following methods available. Methods that are not available cannot be used. You should not modify Maybe
class.
You may use the method descriptor as an inspiration for future exercises to make your method more flexible.
All the methods below are public
.
No other public
methods are available.
Method | Description |
---|---|
static <T> Maybe<T> of (T val) |
Returns a new Maybe<T> depending on the value of val .
|
static <T> Maybe<T> some (T val) |
Returns a new Maybe<T> with the content val regardless if val is null or not. |
static <T> Maybe<T> none (T val) |
Returns a the singleton NONE without any value inside. |
<U> Maybe<U> map (Transformer<? super T, ? extends U> func) |
Transform this.val (if any) using func and return a new Maybe<U> .
|
Maybe<T> filter (BooleanCondition<? super T> pred) |
Transform this.val (if any) depending on the result of pred .
|
<U> Maybe<U> flatMap (Transformer<? super T, ? extends Maybe<? extends U>> func) |
Transform this.val (if any) using func and return a new Maybe<U> .
|
T orElse (Producer<? extends T> prod) |
Returns this.val (if any).
|
void ifPresent (Consumer<? super T> cons) |
Consumes this.val (if any).
|
Being Lazy
Programming languages such as Scala support lazy values, where the expression that produces a lazy value is not evaluated until the value is needed. Lazy value is useful for cases where producing the value is expensive, but the value might not eventually be used. Java, however, does not provide a similar abstraction. So, you are going to build one.
You are required to design a single Lazy<T>
class as part of the cs2030s.fp
package with two instance fields and no class fields.
You are not allowed to add additional instance/class fields to Lazy<T>
.
1 2 3 4 5 6 |
|
While you cannot add new fields, you should make the current field more flexible whenever possible. Furthermore, in all discussion below, the method signature given may not be the most flexible. Your task is to determine if they can be made more flexible. If they can, you should use the most flexible type while minimizing the number of type parameters by using wildcards.
Constraints
You should minimize the use of conditional statements and conditional expressions. In many cases, this can be done by using the appropriate methods from Maybe<T>
. You are also not allowed to have nested class within Lazy<T>
to avoid conditional statements/expressions by using polymorphism.
If you have done the design correctly, you will have no conditional statements/expressions except for the boolean equals(Object)
method.
The basic idea is that we can match the concept of None<T>
to a lazy value that is not yet computed and the concept of Some<T>
to a lazy value that is already computed.
The proper name for this is that they are isomorphic.
Tasks
Task 1: Basic
Define a generic Lazy
class to encapsulate a value with the following operations:
static of(T v)
method that initializes theLazy
object with the given value.static of(Producer<T> s)
method that takes in a producer that produces the value when needed.get()
method that is called when the value is needed. If the value is already available, return that value; otherwise, compute the value and return it. The computation should only be done once for the same value.toString()
: returns"?"
if the value is not yet available; returns the string representation of the value otherwise.- You are encouraged to use
String.valueOf(obj)
instead ofobj.toString()
to avoid runtime error whenobj
isnull
.
- You are encouraged to use
Immutable?
For our Lazy<T>
to be immutable and to make the memoization of the value transparent, toString
should call get()
and should never return "?"
. We break the rules of immutability and encapsulation here, just so that it is easier to debug and test the laziness of your implementation.
Sample Usage | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
|
You can test this more comprehensively by running without compilation warning/error and all tests printing ok
.
Make sure your code follows the CS2030S Java style.
Test1.java | |
---|---|
1 2 3 |
|
Task 2: Map and FlatMap
Now let's add the map
and flatMap
method. Remember that Lazy
should not evaluate anything until get()
is called, so the function f
passed into Lazy
through map
and flatMap
should not be evaluated until get()
is called. Furthermore, they should be evaluated once. That result from map
and flatMap
, once evaluated, should be cached (also called memoized), so that function must not be called again.
Sample Usage | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
|
You can test this more comprehensively by running without compilation warning/error and all tests printing ok
.
Make sure your code follows the CS2030S Java style.
Test2.java | |
---|---|
1 2 3 |
|
Task 3: Filter and Equality
Write a filter
method, which takes in a BooleanCondition
and lazily tests if the value passes the test or not. Returns a Lazy<Boolean>
object. The BooleanCondition
must be executed at most once.
Then write an equals
, which overrides the equals
method in the Object
class. equals
is an eager operation that causes the values to be evaluated (if not already cached). equals
should return true
only if both objects being compared are Lazy
and the value contains within are equals (according to their equals()
methods).
Sample Usage | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
|
You can test this more comprehensively by running without compilation warning/error and all tests printing ok
.
Make sure your code follows the CS2030S Java style.
Test3.java | |
---|---|
1 2 3 |
|
Task 4: Combine
We have provided an interface called Combiner<S, T, R>
in cs2030s.fp
, with a single combine
method to combine two values, of type S
and T
respectively, into a result of type R
.
Add a method called combine
into Lazy
. The combine
method takes in another Lazy
object and a Combiner
implementation to lazily combine the two Lazy objects (which may contain values of different types) and return a new Lazy
object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
You can test this more comprehensively by running without compilation warning/error and all tests printing ok
.
Make sure your code follows the CS2030S Java style.
Test4.java | |
---|---|
1 2 3 |
|
Skeleton for Programming Exercise 5
We provide .class
files for the functional interfaces as well as Maybe<T>
.
There are also template files for Lazy.java
and Combiner.java
in cs2030s/fp
directory.
Some files (e.g., Test1.java
, Test2.java
, CS2030STest.java
, etc) are provided for testing.
You may edit them to add your own test cases, but we will be using our own version for testing.
While there is no given public test cases for it, we will test your code with hidden test cases that checks for flexible type.
Additionally, minimize the number of type parameter by using wildcards.
Lastly, ensure that you use @SuppressWarnings
as needed.
Following CS2030S Style Guide
You should make sure your code follows the given Java style guide.
Further Deductions
Additional deductions may be given for other issues or errors in your code. These deductions may now be unbounded, up to 5 marks. This include but not limited to
- run-time error.
- failure to follow instructions.
- improper designs (e.g., not following good OOP practice).
- not comenting
@SuppressWarnings
. - misuse of
@SuppressWarnings
(e.g., not necessary, not in smallest scope, etc).
Documentation (Optional)
Documenting your code with Javadoc is optional for Programming Exercise 6. It is, however, always a good practice to include comments to help readers understand your code.