6

So, after playing around with Java generics a bit, to get a deeper understanding of their capabilities, I decided to try to implement the curried version of the composition function, familiar to functional programmers. Compose has the type (in functional languages) (b -> c) -> (a -> b) -> (a -> c). Doing currying arithmetic functions wasn't too hard, since they are just polymorphic, but compose is a higher order function, and its proven taxing to my understanding of generics in Java.

Here is the implementation I've created so far:

public class Currying {

  public static void main(String[] argv){
    // Basic usage of currying
    System.out.println(add().ap(3).ap(4));
    // Next, lets try (3 * 4) + 2
    // First lets create the (+2) function...
    Fn<Integer, Integer> plus2 = add().ap(2);
    // next, the times 3 function
    Fn<Integer, Integer> times3 = mult().ap(3);
    // now we compose them into a multiply by 2 and add 3 function
    Fn<Integer, Integer> times3plus2 = compose().ap(plus2).ap(times3);
    // now we can put in the final argument and print the result
    // without compose:
    System.out.println(plus2.ap(times3.ap(4)));
    // with compose:
    System.out.println(times3plus2.ap(new Integer(4)));
  }

  public static <A,B,C> 
                Fn<Fn<B,C>, // (b -> c) -> -- f
                Fn<Fn<A,B>, // (a -> b) -> -- g
                Fn<A,C>>>   // (a -> c)
                compose(){
    return new  Fn<Fn<B,C>, 
                Fn<Fn<A,B>, 
                Fn<A,C>>> () {
      public Fn<Fn<A,B>, 
             Fn<A,C>> ap(final Fn<B,C> f){
        return new Fn<Fn<A,B>, 
                   Fn<A,C>>() {
          public Fn<A,C> ap(final Fn<A,B> g){
            return new Fn<A,C>(){
              public C ap(final A a){
                return f.ap(g.ap(a));
              }
            };
          }
        };
      }
    };
  }

  // curried addition
  public static Fn<Integer, Fn<Integer, Integer>> add(){
    return new Fn<Integer, Fn<Integer, Integer>>(){
      public Fn<Integer,Integer> ap(final Integer a) {
        return new Fn<Integer, Integer>() {
          public Integer ap(final Integer b){
            return a + b;
          }
        };
      }
    };
  }

  // curried multiplication
  public static Fn<Integer, Fn<Integer, Integer>> mult(){
    return new Fn<Integer, Fn<Integer, Integer>>(){
      public Fn<Integer,Integer> ap(final Integer a) {
        return new Fn<Integer, Integer>() {
          public Integer ap(final Integer b){
            return a * b;
          }
        };
      }
    };
  }
}

interface Fn<A, B> {
  public B ap(final A a);
}

The implementations of add, mult, and compose all compile just fine, but I find myself having a problem when it comes to actually using compose. I get the following error for line 12 (the first usage of compose in main):

Currying.java:12: ap(Fn<java.lang.Object,java.lang.Object>) in 
Fn<Fn<java.lang.Object,java.lang.Object>,Fn<Fn<java.lang.Object,java.lang.Object>,Fn<java.lang.Object,java.lang.Object>>>
cannot be applied to (Fn<java.lang.Integer,java.lang.Integer>)
    Fn<Integer,Integer> times3plus2 = compose().ap(plus2).ap(times3);

I assume this error is because generic types are invariant, but I am not sure how to solve the problem. From what I've read, wildcard type variables can be used to alleviate invariance in some cases, but I'm not sure how to use it here or even whether it will be useful.

Disclaimer: I have no intention of writing code like this in any real project. This is a fun "can it be done" kind of thing. Also, I made the variable names brief in defiance of standard Java practice because otherwise this example becomes an even more of an incomprehensible wall of text.

deontologician
  • 2,764
  • 1
  • 21
  • 33

3 Answers3

4

The basic problem here is that in the original call to compose(), there is no way for the compiler to infer the bindings of A, B, and C, so it assumes them all to be Object. You can fix it by specifying the type bindings explicitly:

Fn<Integer, Integer> times3plus2 = 
    Currying.<Integer, Integer, Integer>compose().ap(plus2).ap(times3);

Of course, then you lose the clarity that comes from type inference. If you need type inference, you could define some intermediate classes to do the inferring:

public static ComposeStart compose() {
    return new ComposeStart();
}

class ComposeStart {
    public <B,C> ComposeContinuation<B,C> ap(Fn<B,C> f) {
        return new ComposeContinuation<B, C>(f);
    }
}

class ComposeContinuation<B, C> {
    private final Fn<B,C> f;

    ComposeContinuation(Fn<B,C> f) {
        this.f = f;
    }

    public <A> Fn<A,C> ap(final Fn<A,B> g) {
        return new Fn<A,C>() {
            public C ap(A a) {
                return f.ap(g.ap(a));
            }
        };
    }
}

However, then the intermediate steps of currying are no longer Fns.

Russell Zahniser
  • 16,188
  • 39
  • 30
  • Ok! Thanks to your answer I think I found a solution that solves the problem! – deontologician Jan 18 '12 at 19:10
  • I think that's the solution that satisfies my requirements the best (the non-inferred version). It doesn't get inference, but I think not having to define an extra class is worth it. – deontologician Jan 18 '12 at 20:48
0

I've implemented this functionality myself in terms of a generic length-n chain of function calls.

public static final <X, Y> Chainer<X, Y> chain(
        final Function<X, Y> primary
) {
    return new Chainer<X, Y>(primary);
}

private static final class FunctionChain<IN, OUT> implements Function<IN, OUT> {

    @SuppressWarnings("rawtypes")
    private final List<Function> chain =  new LinkedList<Function>();

    private FunctionChain(@SuppressWarnings("rawtypes") final List<Function> chain) {
        this.chain.addAll(chain);
    }

    @SuppressWarnings("unchecked")
    @Override
    public OUT apply(final IN in) {
        Object ret = in;
        for (final Function<Object, Object> f : chain) {
            ret = f.apply(ret);
        }
        return (OUT) ret;
    }
}

public static final class Chainer<IN, OUT> {
    @SuppressWarnings("rawtypes")
    private final LinkedList<Function> functions = new LinkedList<Function>();

    @SuppressWarnings("unchecked")
    private Chainer(@SuppressWarnings("rawtypes") final Function func) {
        then(func);
    }

    @SuppressWarnings("unchecked")
    public <OUT2> Chainer<IN, OUT2> then(final Function<OUT, OUT2> func) {
        if (func instanceof FunctionChain) {
            functions.addAll(((FunctionChain<?, ?>)func).chain);
        } else {
            functions.add(func);
        }
        return (Chainer<IN, OUT2>) this;
    }

    @SuppressWarnings("unchecked")
    public Function<IN, OUT> build() {
        // If empty, it's a noop function. If one element, there's no need for a chain.
        return new FunctionChain<IN, OUT>(functions);
    }
}

public static final <X, Y, Z> Function<X, Z> combine(
        final Function<X, Y> primary,
        final Function<Y, Z> secondary
) {
    return chain(primary).then(secondary).build();
}

I would contend that this qualifies as a greater abuse of generics, since the Chainer class only uses generics to ensure that subsequent .then() calls are properly typed based on the last function supplied, and the functions are just stored in a list in what is known to be a safe calling order for later, but it does work and it does generalize well: chain(first).then(second).then(third).then(fourth).build() is completely valid with this approach.

Just be be explicit, this is based around Function from guava, but should port over to any Function interface just fine.

ancsik
  • 11
  • 1
0

Thanks to Russell Zahniser's insight that I wasn't giving Java enough information to work with, I changed the layout a bit so that we instantiate a "Composer" object with the appropriate type variables filled in. Here is my current working solution:

interface Fn<A, B> {
  public B ap(final A a);
}

public class Currying {

  public static void main(String[] argv){
    // Basic usage of currying
    System.out.println(add().ap(3).ap(4));
    // Next, lets try (3 * 4) + 2
    // First lets create the (+2) function...
    Fn<Integer, Integer> plus2 = add().ap(2);
    // next, the times 3 function
    Fn<Integer, Integer> times3 = mult().ap(3);
    // now we compose them into a multiply by 2 and add 3 function
    Fn<Integer, Integer> times3plus2 = new Composer<Integer,Integer,Integer>()
      .compose().ap(plus2).ap(times3);
    // without compose
    System.out.println(plus2.ap(times3.ap(4)));
    // with compose
    System.out.println(times3plus2.ap(4));
  }

  static class Composer<A,B,C> { 
    public
      Fn<Fn<B,C>, // (b -> c) -> -- f
      Fn<Fn<A,B>, // (a -> b) -> -- g
      Fn<A,C>>>   // (a -> c)
      compose(){
      return new Fn<Fn<B,C>, 
        Fn<Fn<A,B>, 
        Fn<A,C>>> () {
        public Fn<Fn<A,B>, 
          Fn<A,C>> ap(final Fn<B,C> f){
          return new Fn<Fn<A,B>, 
            Fn<A,C>>() {
            public Fn<A,C> ap(final Fn<A,B> g){
              return new Fn<A,C>(){
                public C ap(final A a){
                  return f.ap(g.ap(a));
                }
              };
            }
          };
        }
      };
    }
  }

  public static Fn<Integer, Fn<Integer, Integer>> add(){
    return new Fn<Integer, Fn<Integer, Integer>>(){
      public Fn<Integer,Integer> ap(final Integer a) {
        return new Fn<Integer, Integer>() {
          public Integer ap(final Integer b){
            return a + b;
          }
        };
      }
    };
  }

  public static Fn<Integer, Fn<Integer, Integer>> mult(){
    return new Fn<Integer, Fn<Integer, Integer>>(){
      public Fn<Integer,Integer> ap(final Integer a) {
        return new Fn<Integer, Integer>() {
          public Integer ap(final Integer b){
            return a * b;
          }
        };
      }
    };
  }
}
deontologician
  • 2,764
  • 1
  • 21
  • 33
  • Actually, if manually specifying the types like this is OK, your original code would have worked if you just changed the offending line to `Fn times3plus2 = Currying.compose().ap(plus2).ap(times3);` – Russell Zahniser Jan 18 '12 at 20:27
  • Ah yeah, that would work fine! I don't put too much stock in the Java type inference engine. – deontologician Jan 18 '12 at 20:42