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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With