I want to define a Functor class in Java. This works:
//a Function
public interface F {
public R apply(A a);
}
public interface Functor {
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 {
public ToInstance fmap(FromInstance instance, F f);
}
public class ListFunctor implements Functor, List> {
@Override
public List fmap(List instance, F f) {
List result = new ArrayList();
for(A a: instance) result.add(f.apply(a));
return result;
}
}
List stringList = Arrays.asList("one","two","three");
ListFunctor functor = new ListFunctor();
List 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
, you can construct a F
from it when you have a FooFunctor
defined as shown above:
public interface F {
public B apply(A a);
public F lift(
Functor functor);
}
public abstract class AbstractF implements F {
@Override
public abstract B apply(A a);
@Override
public F lift(
final Functor functor) {
return new AbstractF() {
@Override
public ToInstance apply(FromInstance fromInstance) {
return functor.fmap(fromInstance, AbstractF.this);
}
};
}
}
public interface Functor {
public ToInstance fmap(FromInstance instance, F f);
}
public class ListFunctor implements Functor, List> {
@Override
public List fmap(List instance, F f) {
List result = new ArrayList();
for (A a : instance) {
result.add(f.apply(a));
}
return result;
}
}
//Usage:
F strLenF = new AbstractF() {
public Integer apply(String a) {
return a.length();
}
};
//Whoa, magick!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
F,List> liftedF = strLenF.lift(new ListFunctor());
List stringList = Arrays.asList("one", "two", "three");
List 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