Ex 5: Call Me Maybe
Basic Information
- Deadline: 15 October 2024, Tuesday, 23:59 SGT
- Difficulty: ★★★★★★
Prerequisite
- Caught up to Unit 29 of Lecture Notes.
- Familiar with CS2030S Java style guide.
Goal
This is a continuation of Programming Exercise 4. In Exercise 4, we have constructed a generic class Some<T>
, which is a container for an item of type T
. Beyond being an exercise for teaching about generics, Some<T>
is not a very useful type. In Programming Exercises 5 and 6, we are going to modify Some<T>
into two more useful and general classes. We are going to build our own Java packages using these useful classes.
Java Package
Java package mechanism allows us to group relevant classes and interfaces under a namespace. You have seen two packages so far: java.util
, where we import List
and Arrays
from as well as java.lang
where we import the Math
class. These are provided by Java as standard libraries. We can also create our package and put the classes and interfaces into the same package. We (and the clients) can then import and use the classes and interfaces that we provide.
Java package provides a higher layer of abstraction barrier. We can designate a class to be used outside a package by prefixing the keyword class
with the access modifier public
. This is another advantage of public class. The previous advantage of a public class is that it must have the same name as the file. So java compiler knows where to search. We can further fine-tune which fields and methods are accessible from other classes in the same package using the protected
access modifier.
You can read more about java packages and the protected
modifier yourself through Oracle's Java tutorial.
As a summary, the access levels are as follows.
Modifier | Access from same class | Access from same package (or same directory) |
Access from subclass (even in other directory) |
Access from other class (even in other directory) |
---|---|---|---|---|
public |
||||
protected |
||||
no modifier | ||||
private |
We will create a package named cs2030s.fp
to be used for this and the next few exercises.
First, we need to add the line:
1 |
|
on top of every .java
file that we would like to include in the package.
Second, the package name is typically written in a hierarchical manner using the "." notation. The name also indicates the location of the .java
files and the .class
files. For this reason, you can no longer store the .java
files under ex5-username
directly. Instead, you should put them in a subdirectory called cs2030s/fp
under ex5-username
. To start, our cs2030s.fp
package will contain the one interface Transformer
that you have written in Programming Exercise 4.
If you have not made Transformer
a public
class, you should do it now.
1 2 3 |
|
Finally, to compile your code, under your ex5-username
directory, run:
1 |
|
Note, you may see some compilation error because the java files in ex5-username
directory may fail to compile. That is normal. TestN.java
can only compile when you finish some tasks below. If you have set up everything correctly, you should be able to run the following in JShell from your ex5-username
directory:
1 |
|
Checkpoint
If you have done this correctly, your directory structure should look something like the following:
1 2 3 4 5 6 7 8 9 10 |
|
Tasks
Eventually, we will be creating a static nested class Some<T>
that is nested inside the Maybe<T>
class.
Maybe<T>
encapsulates the possibility that a value is missing.
Our Maybe<T>
is an option type, a common abstraction in programming languages (java.util.Optional
in Java, option
in Scala, Maybe
in Haskell, Nullable<T>
in C#, etc) that is a wrapper around a value that might be missing. In other words, it represents either some value, or none.
Task 1: More Interfaces
Now, we are going to add three more interfaces into our package:
Producer<T>
is an interface with a singleproduce
method that takes in no parameter and returns a value of typeT
.Consumer<T>
is an interface with a singleconsume
method that takes in a parameter of typeT
and returns nothing.BooleanCondition<T>
is an interface with a singletest
method that takes in a parameter of typeT
and returns a primitiveboolean
value.
Checkpoint
If you have done this correctly, your directory structure should look something like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
If you have set up everything correctly, you should be able to run the following in JShell without errors (remember to always compile your code first!).
Sample Usage | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Task 2: Some Packaging
There is minimal amount of code to be added here. We will be mainly be doing a rearrangement of code.
- Copy your implementation of
Some.java
intolab5-username/cs2030s/fp
directory if you have not done so. - Add
package cs2030s.fp;
as the first line onSome.java
. -
Rename
Some.java
intoMaybe.java
. This entails some other changes too:- Rename some occurrences of
Some
intoMaybe
especially the return types. - Do NOT change the name of the factory method
some
.
Checkpoint
If you have done this correctly, your directory structure should look something like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13
labX-username/ ├─ cs2030s/ │ └─ fp/ │ ├─ BooleanCondition.java │ ├─ Consumer.java │ ├─ Maybe.java │ ├─ Producer.java │ └─ Transformer.java ├─ CS2030STest.java ├─ Test1.java ├─ Test2.java ├─ : └─ ...
Your
Maybe.java
should still contain the classSome<T>
.cs2030s/fp/Maybe.java 1 2 3
public class Some<T> { : }
- Rename some occurrences of
-
Change
public class Some<T>
toprivate static final class Some<T> extends Maybe<T>
.- Then wrap it inside the outer class
public abstract class Maybe<T>
. - Move
public static <T> Maybe<T> some(T value)
fromSome<T>
toMaybe<T>
.
Checkpoint
At this point,
Some<T>
is a static nested class insideMaybe<T>
. But codes from outside of the package cannot seeSome<T>
and onlyMaybe<T>
. SinceMaybe<T>
does not have any known method, we need to add abstract methods.cs2030s/fp/Maybe.java 1 2 3 4 5 6 7 8 9 10
public abstract class Maybe<T> { : public static <T> Maybe<T> some(T value) { : // implementation omitted } private static final class Some<T> extends Maybe<T> { : // code from the old version of Some.java } }
- Then wrap it inside the outer class
-
Add abstract method descriptor that appears in
Some<T>
toMaybe<T>
unless these method descriptor already available inObject
. -
Finally, we need to handle
null
values inSome<T>
andMaybe<T>
.public static <T> Maybe<T> some(T value)
acceptsnull
and simply store thenull
value in the field.- Two
Some<T>
instances are equal (as decided by their respectiveequals(Object)
method) if either one (or both) of the following condition is true.- The content are both
null
. - The content are equal as decided by their respective
equals(Object)
method.
- The content are both
map
There is no need to specially handle
null
inmap
. In particular, if theTransfomer
inmap
returnsnull
, we will simply use thenull
value.
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
|
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 3: None Other than You
Now we want to add None<T>
class as another private static nested class inside Maybe<T>
.
This class is also a subtype of Maybe<T>
.
The types None<T>
is an internal implementation details of Maybe<T>
and must not be used directly by the client. Hence, it must be declared private. Here is the requirement for None<T>
.
None<T>
is a generic private inner class that inherits fromMaybe<T>
.None<T>
has no instance field.None<T>
has private constructor that takes in no argument.None<T>
overrides theequals(Object)
method.- Any instance of
None<T>
is equal to any other instance ofNone<T>
. - Note that
Some<T>
should never be equal toNone<T>
.
- Any instance of
None<T>
overrides thetoString()
method.- It simply prints
[]
.
- It simply prints
None<T>
overrides themap
method fromMaybe<T>
.- This simply returns itself.
None<T>
(and by extensionMaybe<T>
) must be immutable up toT
.- But you do not have to make the class a
final
class.
- But you do not have to make the class a
Additionally, we need to add the following factory methods in Maybe<T>
.
- Add the factory method
none()
that returns an instance ofNone<T>
.- There should only be ONE instance of
None<T>
such that multiple calls tonone()
should return the same instance. - You may add
@SuppressWarnings
here with explanation on why it is safe.
- There should only be ONE instance of
- Add the factory method
of
that returns:- an instance of
None<T>
if the input isnull
. - an instance of
Some<T>
if the input is notnull
.
- an instance of
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
|
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 4: Filtering
We now add the method filter
to Maybe<T>
.
- Add the abstract method
filter
toMaybe<T>
that takes in aBooleanCondition<..>
(type parameter is omitted) as a parameter. - Override the method
filter
inSome<T>
as follows.- If the value is
null
or it failed the test (i.e., the call totest
returnsfalse
), returnNone<T>
. - Otherwise, leaves the
Maybe<T>
untouched and returns theMaybe<T>
as it is.
- If the value is
- Override the method
filter
inNone<T>
as follows.- Always returns a
None<T>
.
- Always returns a
Sample Usage | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
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 5: flatMap
Consider a Transformer
that might return a Maybe<T>
itself (as wordToMaybeInt
above). Using map
on such a Transformer
would lead to a value wrapped around a Maybe
twice. We want to add a method that is not doing this.
- Add the abstract method
flatMap
toMaybe<T>
that takes in aTransfomer<..>
(type parameter is omitted) as a parameter. - Override the method
flatMap
inSome<T>
as follows.- The
Transformer
object transforms the value of typeT
inMaybe<T>
into a value of typeMaybe<U>
, for some typeU
. - The method
flatMap
, however, returns a value of typeMaybe<U>
(instead ofMaybe<Maybe<U>>
as in the case ofmap
). - You may add
@SuppressWarnings
here with explanation on why it is safe.
- The
- Override the method
flatMap
inNone<T>
as follows.- Always returns a
None<T>
.
- Always returns a
Sample Usage | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
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 |
|
Task 6: Back to T
Since Maybe<T>
is an abstraction for a possibly missing value (of type T
), it would be useful to provide methods that decide what to do if the value is missing.
- Add the abstract method
orElse
inMaybe<T>
that takes in aProducer<..>
(type parameter is omitted) as the parameter.- Override the method
orElse
inSome<T>
to return the value inside. - Override the method
orElse
inNone<T>
to return the subtype ofT
produced by the producer.
- Override the method
- Add the abstract method
ifPresent
inMaybe<T>
that takes in aConsumer<..>
(type parameter is omitted) as the parameter.- Override the method
ifPresent
inSome<T>
such that the given consumer consumes the value inside. - Override the method
ifPresent
inNone<T>
that does nothing.
- Override the method
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 |
|
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.
Test5.java | |
---|---|
1 2 3 |
|
Skeleton for Programming Exercise 5
You need to copy the files Some.java
and Transformer.java
from ex4-username
to ex5-username
.
Some files (e.g., Test1.java
, Test2.java
, CS2030STest.java
, etc) are provided for testing.
Do not copy these from ex4-username
.
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.
To check for style, we may need two commands as there are two directories of interest.
Style Check | |
---|---|
1 2 |
|
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 5. It is, however, always a good practice to include comments to help readers understand your code.