I am fairly new to Functional Programming and I am trying to use Lambda features in Java to try doing FP. I know Java is not a good choice for learning Functional but in my office I am restricted to use Java and would love to apply some of these principles there.
I created an Optional monad type thing in Java which looks something like this:
public abstract class Optional<T> implements Monad<T> {
//unit function
public static <T> Optional<T> of(T value) {
return value != null ? new Present<T>(value) : Absent.<T>instance();
}
@Override
public <V> Monad<V> flatMap(Function<T, Monad<V>> function) {
return isPresent() ? function.apply(get()) : Absent.<V>instance();
}
}
I am using this for avoiding nested null
checks in my code, a typical use case where I use this is when I need something like firstNonNull
.
Use:
String value = Optional.<String>of(null)
.or(Optional.<String>of(null)) //call it some reference
.or(Optional.of("Hello")) //other reference
.flatMap(s -> {
return Optional.of(s.toLowerCase());
})
.get();
This works as a charm. Now the question is that how do I combine logging with this? What if I need to know which of these reference was used? This is useful if there is some semantic attached with those references and I need to log that this reference was not found, trying other option.
Log:
some reference is not present and some other business case specific log
Is this possible to achieve in Java? I tried to read some possible solutions from web and found Writer
monad of Haskell, but I got confused and couldn't follow.
EDIT
Link to the gist
A neat solution to this is monoid composition.
Combining monoids
A monoid is an associative binary operation with an identity. Your Optional<A>
type forms a monoid for any A
:
https://functionaljava.ci.cloudbees.com/job/master/javadoc/fj/Monoid.html#firstOptionMonoid--
In your case, the Monoid<Optional<A>>
would be implemented using or
as the sum
and Absent
as the zero
, so yourMonoid.sum(x, y)
should be the same as x.or(y)
.
Now you want to combine that with another monoid--one that consists of your log. So let's say you use a simple String
as your log. Well, String
forms a monoid where sum
is string concatenation and the zero
is the empty string.
You want to combine the String
monoid with the firstOptionMonoid
. For that you need a tuple type. Functional Java has a tuple type called P2
. Here's how you combine two monoids (this should really be added to the Monoid
class; send a pull request!):
import fj.*;
import static fj.P.p;
import static fj.Monoid.*;
public final <A,B> Monoid<P2<A,B>> compose(Monoid<A> a, Monoid<B> b) {
return monoid(
x -> y -> p(a.sum(x._1, y._1), b.sum(x._2, y._2)),
p(a.zero, b.zero));
}
The composite monoid is then (in FJ):
Monoid<P2<Option<A>, String>> m = compose(firstOptionMonoid<A>, stringMonoid)
Now, you don't always want to add the log. You want it to depend on whether a value in the Option
is present or absent. For that you can write a specialized method:
public final P2<Option<A>, String> loggingOr(
P2<Option<A>, String> soFar,
Option<A> additional,
String msg) {
return soFar._1.isDefined ?
soFar :
m.sum(soFar, p(additional, msg))
}
I recommend looking more into monoids in general. They're a very versatile tool, and they're one of the few purely functional constructs that are actually pleasant to use in Java. If you don't mind learning in Scala, I wrote a book called Functional Programming in Scala and the chapter on monoids just happens to be available online for free.
Combining monads
But now you are working with a composite type P2<Option<A>, String>
rather than just Option<A>
, and this type doesn't come with a flatMap
. What you would really want (if Java could do that, but it can't) is to use the Writer<String, _>
monad with a monad transformer like OptionT
. Imagining for a moment that Java could have monad transformers, the type P2<Option<A>, String>
would be equivalent to the type OptionT<Writer<String, _>, A>
, where _
indicates a partially applied type constructor (not valid Java, obviously).
The only solution in Java is to combine these monads in a first-order way:
import fj.data.Writer
public abstract class OptionWriter<W, A> {
abstract Writer<W, Option<A>> writer;
public <B> OptionWriter<W, B> flatMap(Function<A, OptionWriter<B>>) {
...
}
public static <M, T> OptionWriter<M, T> unit(t: T) {
return Writer.unit(Option.unit(t))
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With