Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Logging in functional programming with Java

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

like image 949
Narendra Pathai Avatar asked Nov 01 '22 19:11

Narendra Pathai


1 Answers

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))
  }
}
like image 101
Apocalisp Avatar answered Nov 15 '22 05:11

Apocalisp