17

I want to define a Functor class in Java. This works:

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

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

However the return value of fmap should be not Functor, but the appropriate subclass. Usually this can be encoded with the CRTP, but here I seem to hit a wall because of the additional parameter A. E.g. the following and similar encodings don't work ("type parameter FInst is not within its bounds"):

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

[Clarification]

With "appropriate subclass" I mean the type of the class being called itself. E.g. Lists are functors, so I would like to write something like

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

  @Override
  <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); 
  }  
}

I'm aware that I could write this even with the first definition I gave (because covariant return types are allowed), but I want that the return type "ListFunctor" is enforced by the type system (so that I can't return a FooFunctor instead), which means that the Functor interface needs to return the "self-type" (at least it is called so in other languages).

[Result]

So it seems what I want is impossible. Here is a related blog-post: http://blog.tmorris.net/higher-order-polymorphism-for-pseudo-java/

[Aftermath]

I stumbled over this age-old question of mine, and realized that this was the starting point of the amazing journey with my library highJ, containing much more than a simple Functor. I would have never imagine that people would use this crazy stuff for anything serious, but it happened, and that makes me very happy.

Landei
  • 54,104
  • 13
  • 100
  • 195
  • 1
    Probably bad form to have more than 3 type parameters especially ones that include the other, as the declaration becomes a big mess and no one knows whats going on. – mP. Feb 01 '11 at 12:23
  • 1
    There is a difference concerning the "library writer" side and the usage side. And you can often "hide" generics, even with Java's limited type inference (e.g. by using static methods). – Landei Feb 01 '11 at 12:36

5 Answers5

6
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.

Sergei Tachenov
  • 24,345
  • 8
  • 57
  • 73
  • Very interesting! So it seems we can only "preserve" **either** B **or** the sub-functor-type (which is kind of logical, because we're trying to mangle somehow different "levels" of generics in one definition). – Landei Feb 01 '11 at 11:56
  • One other problem I've noticed with this is that there is no requirement that `FClass` is the implementing class. One could implement a `class BadFunct implements Functor>` – Zoey Hewll Apr 19 '18 at 11:08
2

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

Landei
  • 54,104
  • 13
  • 100
  • 195
  • 1
    Yes, I thought about this approach too, but I didn't like the fact that if you move B to the class level, you're basically limiting your function to exactly one return type. Your previous approach looks better I think. – Sergei Tachenov Feb 01 '11 at 16:30
  • I agree, as it is, this version is pretty useless. On the other hand it does exactly what I want (without casts or reflection) and is easy to understand, so I hope to find a way around the type parameter pain. When I have some more time, I'll see what static methods can do in terms of type inference. – Landei Feb 01 '11 at 17:19
1

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...

Landei
  • 54,104
  • 13
  • 100
  • 195
  • Yes, that's precisely the problem. Basically you are limiting the return type when you call the function, which allows you to do a lot of dangerous things. I am surprised how you were able to get around with that `(I)` cast, though. I didn't understand why Java reported the return type to be incompatible, but I had no idea that a cast would help. – Sergei Tachenov Feb 01 '11 at 13:33
1

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
    }
}
Adam Wall
  • 11
  • 1
  • Thanks for the answer, I started to use a similar approach years ago in my https://github.com/highj/highj library, and other libraries (e.g. Cyclops) are doing the same thing. – Landei Jun 15 '19 at 11:38
0

I think you want to do something that makes no sense (type wise).

interface Getter<Type> {
 Type get();
}

If your application wants a getter that returns Integers, don't give it one that returns Objects.

If you don't know if it will return Objects or Integers you are trying to do something the wrong way.

If YOU KNOW it will return Integers, then wrap the getter so that it casts to integers.

Hope this is what you are looking for .

EDIT: Explanation of why (I think) this can not be done.

Objects have there types set when you use new. Take each type and replace it with a letter. Take any number of another objects and do the same.

What letter do you want your function to return? If the answer is that you want a mix, well then its too late. Types are decided at new, and you are already past new.

mncl
  • 78
  • 7
  • Similar things are working (just look at the definition of java.lang.Enum), and I think what I want is very reasonable (e.g. it would be no problem in Haskell or Scala). The question is how far we can push the boundaries of Java's crippled type system. – Landei Feb 01 '11 at 12:00
  • I'v not managed to build recursive generic constructs using java, while preserving the external type. As someone writes in a post here, you cant capture things from the level above. And I stick with my explanation that you are trying to give your program an object capable of returning things you cant say anything about. Java generics are very static. Once you have set a generic type to a type you cant go back. Before new you have generics. After new you have fixed types. So once your object passes its new, all of its types are decided. – mncl Feb 01 '11 at 15:51
  • Except inference, but that is decided from objects, already past there new statement. None of the objects knows what type the combination (interference) would return. – mncl Feb 01 '11 at 15:52