Java generics: How to encode a Functor interface in Java?

Looking from a different angle, it seems Functor shouldn't be modeled as a "Wrapper" around the data, but actually more like a type-class, which works on the data. This shift of perspective allows to encode everything without a single cast, and absolutely type-safe (but still with a lot of boilerplate):

public interface Functor<A, B, FromInstance, ToInstance> {
    public ToInstance fmap(FromInstance instance, F<A,B> f);
}

public class ListFunctor<A,B> implements Functor<A, B, List<A>, List<B>> {

    @Override
    public List<B> fmap(List<A> instance, F<A, B> f) {
     List<B> result = new ArrayList<B>();
     for(A a: instance) result.add(f.apply(a));
     return result;
    }
}

List<String> stringList = Arrays.asList("one","two","three");
ListFunctor<String,Integer> functor = new ListFunctor<String,Integer>();
List<Integer> intList = functor.fmap(stringList, stringLengthF);
System.out.println(intList);
//--> [3, 3, 5]

It seems I was too focused on packing both FromInstance and ToInstance in one type parameter (e.g. List in ListFunctor), which isn't strictly necessary. However, it's a heavy burden to have now not only A but also B as type parameter, which may make this approach practically unusable.

[Research]

I found a way to make this version at least a little bit useful: This functor can be used to lift a function. E.g. if you have F<String, Integer>, you can construct a F<Foo<String>, Foo<Integer>> from it when you have a FooFunctor defined as shown above:

public interface F<A,B> {
   public B apply(A a);

   public <FromInstance, ToInstance> F<FromInstance, ToInstance> lift(
      Functor<A,B,FromInstance, ToInstance> functor);
}

public abstract class AbstractF<A,B> implements F<A,B> {

    @Override
    public abstract B apply(A a);

    @Override
    public <FromInstance, ToInstance> F<FromInstance, ToInstance> lift(
          final Functor<A, B, FromInstance, ToInstance> functor) {
        return new AbstractF<FromInstance, ToInstance>() {

            @Override
            public ToInstance apply(FromInstance fromInstance) {
                return functor.fmap(fromInstance, AbstractF.this);
            }

        };
    }
}

public interface Functor<A, B, FromInstance, ToInstance> {
    public ToInstance fmap(FromInstance instance, F<A,B> f);
}

public class ListFunctor<A, B> implements Functor<A, B, List<A>, List<B>> {

    @Override
    public List<B> fmap(List<A> instance, F<A, B> f) {
        List<B> result = new ArrayList<B>();
        for (A a : instance) {
            result.add(f.apply(a));
        }
        return result;
    }
}

//Usage:
F<String, Integer> strLenF = new AbstractF<String, Integer>() {
            public Integer apply(String a) {
                return a.length();
            }
        };

//Whoa, magick!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
F<List<String>,List<Integer>> liftedF = strLenF.lift(new ListFunctor<String, Integer>());

List<String> stringList = Arrays.asList("one", "two", "three");
List<Integer> intList = liftedF.apply(stringList);
System.out.println(intList);
//--> [3, 3, 5]

I think it's still not very useful, but at least way cooler than the other attempts :-P


Building on the answer of Sergey, I think I came close to what I wanted. Seems like I can combine his idea with my failed attempt:

public interface Functor<A, Instance extends Functor<?, Instance>> {
    public <B, I extends Functor<B,Instance>> I fmap(F<A,B> f);
}

public class ListFunctor<A> implements Functor<A, ListFunctor<?>> {
  final private List<A> list;
  public ListFunctor(List<A> list) {
     this.list = list;
  }

  @Override
  public <B, I extends Functor<B, ListFunctor<?>>> I fmap(F<A,B> f) {
     List<B> result = new ArrayList<B>();
     for(A a: list) result.add(f.apply(a));
     return (I) new ListFunctor<B>(result);
  }
}

List<String> list = java.util.Arrays.asList("one","two","three");
ListFunctor<String> fs = new ListFunctor<String>(list);
ListFunctor<Integer> fi = fs.<Integer,ListFunctor<Integer>>fmap(stringLengthF);
//--> [3,3,5]

The remaining problem is that I could write e.g. ListFunctor<StringBuilder> fi = fs.<Integer,ListFunctor<StringBuilder>> without complaints from the compiler. At least I can look for a way to hide the ugly guts behind a static method, and to enforce that relation behind the scenes...


public interface Functor<A, FInst extends Functor<A,FInst>> {
    public <B, I extends Functor<B,FInst>> I fmap(F<A,B> f);
}

This code generates an error because when you define I, you define it to be a subclass of Functor<B,FInst>, but the FInst parameter must be a subclass of Functor<B,FInst> in this case, while it is defined above as being a subclass of Functor<A,FInst>. Since Functor<A,FInst> and Functor<B,FInst> aren't compatible, you get this error.

I haven't been able to solve this completely, but I could do at least a half of the job:

import java.util.ArrayList;
import java.util.List;

interface F<A,R> {
   public R apply(A a);
}

interface Functor<A, FClass extends Functor<?, FClass>> {
   public <B> FClass fmap(F<A,B> f);
}

public class ListFunctor<A> implements Functor<A, ListFunctor<?>> {
  final private List<A> list;
  public ListFunctor(List<A> list) {
     this.list = list;
  }

  @Override
  public <B> ListFunctor<B> fmap(F<A,B> f) {
     List<B> result = new ArrayList<B>();
     for(A a: list) result.add(f.apply(a));
     return new ListFunctor<B>(result);
  }
}

This works, and it properly limits the set of allowed return types to ListFunctor, but it doesn't limit it to subclasses of ListFunctor<B> only. You could declare it as returning ListFunctor<A> or any other ListFunctor, and it would still compile. But you can't declare it as returning a FooFunctor or any other Functor.

The main problem with solving the rest of the problem is that you can't limit FClass to subclasses of ListFunctor<B> only, as the B parameter is declared at the method level, not at the class level, so you can't write

public class ListFunctor<A> implements Functor<A, ListFunctor<B>> {

because B doesn't mean anything at that point. I couldn't get it working with the second parameter to the fmap() either, but even if I could, it would just force you to specify the return type twice - once in the type parameter and once more as the return type itself.


Does anyone still use Java and ponder this problem? You might find this useful...

I've been pondering this for a looooong time. I believe I've made something satisfactory. What I would really like to is indeeed impossible in Java.

This is ideal:

interface Functor<T, CONCRETE<A> extends Functor<A, CONCRETE>> {
    CONCRETE<U> fmap(Func<T, U>);
}

Unfortunately, this is make-believe syntax. This kind of thing is possible in C++ with template-template parameters, but not Java.

I was tempted to write this simple thing:

interface Functor<T> {
    Functor<U> fmap(Func<T, U>);
}

This works in some cases, because an implementation can return a covariant return type (for example, List could return a List from this function), but it breaks down when you try passing around generic variables of type "F extends Functor", or a subclass of Functor, etc...

What I ended up doing was introduce a "dummy type variable", like so:

interface Functor<CONCRETE, T> {
    Functor<CONCRETE, U> fmap(Func<T, U>);
}

The "concrete type" should be the type itself, or some dummy type that guarantees the uniqueness of its implementors. Here's an example implementation:

public final class Array<T> implements Functor<Array<?>, T> {
    private final T[] _values;

    @SafeVarargs
    public Array(T... values) {
        _values  = values;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <A, RESULT extends Functor<Array<?>, A>> RESULT fmap(Function<T, A> f) {
        A[] result = (A[]) new Object[_values.length];
        for (int i = 0; i < _values.length; ++i) {
            result[i] = f.apply(_values[i]);
        }

        return (RESULT) new Array<A>(result);
    }
}

The cast to (RESULT) is safe because there can only be one type that matches "Functor, T>", and that's "Array". The disadvantage of this, is that generic code may need to pass around this "CONCRETE" type in a bunch of places, and it makes your signatures unwieldy. For instance:

public class Test {
    public static <CONCRETE, FInt extends Functor<CONCRETE, Integer>, FBool extends Functor<CONCRETE, Boolean>> FBool intToBool(FInt ints) {
        return ints.fmap(x -> x > 5);
    }

    public static void main() {
        Array<Integer> ints = new Array<>();        
        Array<Boolean> bools1 = ints.fmap(x -> x > 5); // this works because Array<> implements fmap covariantly
        Array<Boolean> bools2 = intToBool(ints); // but this also works thanks to our dummy CONCRETE type
    }
}

Tags:

Java

Generics