Alessandro Argentieri
Alessandro Argentieri

Reputation: 3215

Problems with Java Generics while building a monad

I'm playing around Java code in order to create a functional style monad, but I get struck while using generics and the Java compiler gives me a compilation error if I don't cast my object (though Generics would solve this problem!)

This is the usage:

//COMPILATION ERROR! It requires a cast to String
String message = If.of("Hi", s->s!=null).apply(s->s+" guys!").get();

Allowed:

This is my monad:

import java.util.function.Function;
import java.util.function.Predicate;

public class If<T, R> {

  private T t;
  private Predicate predicate;

  private If(T t, Predicate predicate) {
    this.t = t;
    this.predicate = predicate;
  }

  public static<T> If of(T t, Predicate predicate) {
    return new If(t, predicate);
  }

  public If<R,R> apply(Function<T, R> function) {
    if(predicate!=null && predicate.test(t)){
        return new If<R, R>(function.apply(t), null);
    }
    return If.of(this.t, null);
  }

  public T get() {
    return t;
  }

}

Upvotes: 2

Views: 212

Answers (2)

Sweeper
Sweeper

Reputation: 271515

Andy Turner's answer explained the immediate why your current code does not compile, but your monad seems to have a more fundamental problem - it is not very useful.

According to you, the first call to apply should either return the transformed object wrapped in the monad if the condition is true, or the original object wrapped in a monad if the condition is false. But since you are passing null as the condition for both cases, any subsequent calls to apply will cause the second return to be reached, hence always returning the result of the first call to apply.

In fact, it is not possible to either return the original object or the transformed object (not in a useful and type-safe way, anyway). To do it type-safely, you'd need an Either<If<T>, If<R>> (assuming such a type existed). But to extract the values out of an Either, you'd still need an if statement, defeating the purpose of the If<T> class.

Apparently, this is just an exercise to practice writing monads. With that being the case, I recommend that you choose another monad to implement, such as Either. I also suggest you look at this post first.

Upvotes: 2

Andy Turner
Andy Turner

Reputation: 140328

The direct issue is that the return type of the of method is raw:

public static<T> If of(T t, Predicate predicate) {

You presumably need it to be something like:

public static<T> If<T, Something> of(T t, Predicate<T> predicate) {

I would suggest you don't really want to bake the R into the If's type. If you declare it on the method instead, you then have the flexibility to apply it to whatever type you need:

public class If<T> {
  // ...

  public <R> If<R> apply(Function<T, R> function) {
    if(predicate!=null && predicate.test(t)){
      return new If<>(function.apply(t), null);
    }
    return If.of(this.t, null);
  }

  // ...
}

Then your of signature can be simply:

public static<T> If<T> of(T t, Predicate<T> predicate) {

If you want your API to be a bit more flexible, add wildcards:

public static<T> If<T> of(T t, Predicate<? super T> predicate) {

and

public <R> If<R> apply(Function<? super T, ? extends R> function) {

Upvotes: 5

Related Questions