2

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;
  }

}
Alessandro Argentieri
  • 2,901
  • 3
  • 32
  • 62
  • 4
    `public static If of(T t, Predicate predicate) {` -- The return type is raw. – Andy Turner Jan 27 '20 at 08:59
  • 1
    I guess it should be `Predicate` or so. And it would be helpful to include the error message from the compiler... – Gyro Gearless Jan 27 '20 at 09:06
  • 1
    Setting aside the issue of using raw types. What is this monad supposed to do? What would `If.of(1, x -> x > 2).apply(Integer::toString).get()` return? In other words, what happens when the condition is false? Do you expect `null`? – Sweeper Jan 27 '20 at 09:06
  • If the condition is false it returns this. It's like a mapIf it would have existed in Optional monad for example. It applies a transformation only if the Predicate is verified. – Alessandro Argentieri Jan 27 '20 at 09:35
  • I think either you or me, or both of us are very confused. I'm asking about what do you expect `get` will return. Are you saying that `get` should return a `If`? `get` is not similar to an `Optional.map`. Your `apply` is similar to `Optional.map`. When you `get` an empty `Optional`, you get an exception, right? So what I'm asking is, what do you want to happen when you `get` an `If` when the condition is false? – Sweeper Jan 27 '20 at 09:42
  • Sorry for the misunderstood. The method get() must return the wrapped object t (it can be the original object or the transformed object if the Predicate is true) – Alessandro Argentieri Jan 27 '20 at 09:46
  • Upon further examination of your code, I feel like this monad is kind of useless. You can only meaningfully call `apply` _once_, Any further calls to `apply` will cause the second `return` to execute, so you'll always get back the original object? Is this actually intended or you haven't thought this through? – Sweeper Jan 27 '20 at 09:57
  • 1
    And no, `get` can't return _either_ the original object or the transformed object, because they could be different types. The original object is of type `T`, whereas the transformed object is of type `R`. You could use an `Either` monad here, but that kind of defeats the purpose of this `If`, right? Because you still have to check which one it is that is returned. What is it that you are actually trying to accomplish with this `If` monad? If this is just an exercise for monads, there are many other monads that you can write. – Sweeper Jan 27 '20 at 10:04
  • Yes it' just an exercise to get deep into monads and generics. Thanks! If you transform this comment into a response I'll vote it as the right answer. – Alessandro Argentieri Jan 27 '20 at 10:30

2 Answers2

5

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) {
Andy Turner
  • 137,514
  • 11
  • 162
  • 243
2

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.

Sweeper
  • 213,210
  • 22
  • 193
  • 313