4

I'm trying to implement Church Numerals in Java 1.8. My first attempt was:

import java.util.function.UnaryOperator;

@FunctionalInterface
public interface ChurchNumeral {
  public static ChurchNumeral valueOf(int n) {
    if (n < 0) {
      throw new IllegalArgumentException("Argument n must be non-negative.");
    }
    if (n == 0) {
      return (f, arg) -> arg;
    }
    return (f, arg) -> f(valueOf(n-1).apply(f, arg));
  }

  <T> T apply(UnaryOperator<T> f, T arg);
}

This fails because the functional method has a type parameter. (Specifically, the lines with lambda expressions give the error: "Illegal lambda expression: Method apply of type ChurchNumeral is generic".)

Based on answers to related questions on the use of generics with functional interfaces, I tried parameterizing the class:

import java.util.function.UnaryOperator;

@FunctionalInterface
public interface ChurchNumeral<T> {                // This line changed.
  public static ChurchNumeral<?> valueOf(int n) {  // This line changed.
    if (n < 0) {
      throw new IllegalArgumentException("Argument n must be non-negative.");
    }
    if (n == 0) {
      return (f, arg) -> arg;
    }
    return (f, arg) -> f(valueOf(n-1).apply(f, arg));
  }

  T apply(UnaryOperator<T> f, T arg);              // This line changed.
}

The first lambda expression now compiles, but the second one fails with this error:

The method apply(UnaryOperator, capture#1-of ?) in the type ChurchNumeral is not applicable for the arguments (UnaryOperator, Object)

Additionally, I don't want to have different versions of ChurchNumeral.ZERO for every possible function/argument type.

Any suggestions?

Misha
  • 27,433
  • 6
  • 62
  • 78
Ellen Spertus
  • 6,576
  • 9
  • 50
  • 101

2 Answers2

4
static interface Church<T> extends UnaryOperator<UnaryOperator<T>> {

    static <T> Church<T> of(int n) {
        if (n < 0) {
            throw new IllegalArgumentException();
        } else if (n == 0) {
            return f -> (t -> t);
        } else {
            return sum(f -> f, Church.of(n-1));
        }
    }

    static <T> Church<T> sum(Church<T> a, Church<T> b) {
        return f -> b.apply(f).andThen(a.apply(f))::apply;
    }
}

public static void main(String[] args) {
    Church<Integer> five = Church.of(5);
    Church<Integer> three = Church.of(3);
    Church<Integer> eight = Church.sum(five, three);

    assert 3 == three.apply(x -> x + 1).apply(0);
    assert 5 == five.apply(x -> x + 1).apply(0);
    assert 8 == eight.apply(x -> x + 1).apply(0);
}

Edit: If you want apply(UnaryOperator<T> f, T arg) right in the Church interface so you would be able to call .apply(x->x+1,0) instead of .apply(x->x+1).apply(0), you can add a default method like this to the above Church interface:

default T apply(UnaryOperator<T> f, T t) {
    return this.apply(f).apply(t);
}

Edit 2: Here's the updated class that can convert between different types. I also added a mul method to multiply just to see how it works:

static interface Church<T> extends UnaryOperator<UnaryOperator<T>> {

    static <T> Church<T> of(int n) {
        if (n < 0) {
            throw new IllegalArgumentException();
        } else if (n == 0) {
            return zero();
        } else {
            return sum(one(), Church.of(n - 1));
        }
    }

    static <T> Church<T> zero() {
        return f -> (t -> t);
    }

    static <T> Church<T> one() {
        return f -> f;
    }

    static <T> Church<T> sum(Church<T> a, Church<T> b) {
        return f -> b.apply(f).andThen(a.apply(f))::apply;
    }

    static <T> Church<T> mul(Church<T> a, Church<T> b) {
        return f -> a.apply(b.apply(f))::apply;
    }

    default <U> Church<U> convert() {
        return (Church<U>) this;
    }
}

public static void main(String[] args) {
    Church<Integer> zero = Church.zero();
    Church<Integer> five = Church.of(5);
    Church<Integer> three = Church.of(3);
    Church<Integer> eight = Church.sum(five, three);
    Church<Integer> fifteen = Church.mul(three, five);

    assert 0 == zero.apply(x -> x + 1).apply(0);
    assert 3 == three.apply(x -> x + 1).apply(0);
    assert 5 == five.apply(x -> x + 1).apply(0);
    assert 8 == eight.apply(x -> x + 1).apply(0);
    assert 15 == fifteen.apply(x -> x + 1).apply(0);

    Church<String> strOne = Church.one();     
    Church<String> strThree = three.convert();  // make Church<String>
                                                // from a Church<Integer>

    assert "foo:bar".equals(strOne.apply("foo:"::concat).apply("bar"));
    assert "foo:foo:foo:bar".equals(strThree.apply("foo:"::concat).apply("bar"));

}
Misha
  • 27,433
  • 6
  • 62
  • 78
  • Is there a way to do it so I don't need to create a ChurchNumeral for every possible type? I'd like to be able to apply ZERO (for example) to any UnaryOperator and argument of type T. – Ellen Spertus Mar 14 '15 at 01:45
  • @espertus I don't think you can do this directly while still using lambdas. Functional interface cannot represent a generic method. But you can fake it by having zero() as a method instead of a constant. You can also have a convert() method that does an unchecked conversion and it should work fine. See updated answer. – Misha Mar 16 '15 at 23:36
2

Is there a way to do it so I don't need to create a ChurchNumeral for every possible type? I'd like to be able to apply ZERO (for example) to any UnaryOperator and argument of type T

I am assuming you mean you want to do something like this:

ChurchNumeral five = ChurchNumeral.valueOf(5);
five.apply(s -> s + s, "s");
five.apply(Math::sqrt, Double.MAX_VALUE);

which means that the method signature in your first example:

<T> T apply(UnaryOperator<T> f, T arg);

is the one that is needed.

However, you can't use a lambda expression for a functional interface, if the method in the functional interface has type parameters.

A workaround is to create a subinterface which is compatible with lambdas and delegate the calls to apply to its method, as shown below.

public static void main(String[]a){
    ChurchNumeral five = ChurchNumeral.valueOf(5);
    System.out.println(five.apply(s -> s + s, "s"));
    System.out.println(five.apply(Math::sqrt, Double.MAX_VALUE));
}
@FunctionalInterface
private interface ChurchNumeralT<T> extends ChurchNumeral {
    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Override
    default<U> U apply(UnaryOperator<U> f, U arg){
        return (U)((ChurchNumeralT)this).tapply(f, arg);
    }
    T tapply(UnaryOperator<T> f, T arg);
}
public interface ChurchNumeral {

    <T> T apply(UnaryOperator<T> f, T arg);

    static ChurchNumeral valueOf(int n) {
        if (n < 0) {
            throw new IllegalArgumentException("Argument n must be non-negative.");
        }
        if (n == 0) {
            return (ChurchNumeralT<?>)(f, arg) -> arg;
        }
        return (ChurchNumeralT<?>)(f, arg) -> f.apply(valueOf(n - 1).apply(f, arg));
    }
}
Community
  • 1
  • 1
Alex - GlassEditor.com
  • 14,957
  • 5
  • 49
  • 49
  • Thank you. FYI, I was able to remove "rawtypes" from @SuppressWarnings by replacing the cast to ChurchNumeralT to a cast to ChurchNumeralT. – Ellen Spertus Mar 17 '15 at 21:14