【问题标题】:How to implement Church Numerals using Java 1.8如何使用 Java 1.8 实现教堂数字
【发布时间】:2015-05-16 13:36:32
【问题描述】:

我正在尝试在 Java 1.8 中实现教堂数字。我的第一次尝试是:

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

这失败了,因为函数方法有一个类型参数。 (具体来说,带有 lambda 表达式的行给出了错误:“Illegal lambda expression: Method apply of type ChurchNumeral is generic”。)

根据对功能接口使用泛型的相关问题的回答,我尝试对类进行参数化:

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

第一个 lambda 表达式现在可以编译,但第二个失败并出现以下错误:

方法 apply(UnaryOperator, capture#1-of ?) 在 类型 ChurchNumeral 不适用于参数 (一元运算符,对象)

此外,我不希望每个可能的函数/参数类型都有不同版本的 ChurchNumeral.ZERO。

有什么建议吗?

【问题讨论】:

    标签: java generics lambda java-8 church-encoding


    【解决方案1】:
    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);
    }
    

    编辑:如果您希望 apply(UnaryOperator&lt;T&gt; f, T arg) 直接出现在 Church 界面中,这样您就可以调用 .apply(x-&gt;x+1,0) 而不是 .apply(x-&gt;x+1).apply(0),您可以在上面添加这样的默认方法教会界面:

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

    编辑 2: 这是可以在不同类型之间转换的更新后的类。我还添加了一个 mul 方法来进行乘法运算,看看它是如何工作的:

    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"));
    
    }
    

    【讨论】:

    • 有没有办法做到这一点,所以我不需要为每种可能的类型创建一个 ChurchNumeral ?我希望能够将零(例如)应用于任何 UnaryOperator 和 T 类型的参数。
    • @espertus 我不认为你可以在仍然使用 lambdas 的同时直接执行此操作。功能接口不能表示泛型方法。但是您可以通过将 zero() 作为方法而不是常量来伪造它。您还可以使用 convert() 方法进行未经检查的转换,它应该可以正常工作。查看更新的答案。
    【解决方案2】:

    有没有办法做到这一点,所以我不需要为 每种可能的类型?我希望能够申请零(例如) 到任何 UnaryOperator 和 T 类型的参数

    我假设你的意思是你想做这样的事情:

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

    这意味着您的第一个示例中的方法签名:

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

    是需要的。

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

    一种解决方法是创建一个与 lambda 兼容的子接口,并将对 apply 的调用委托给它的方法,如下所示。

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

    【讨论】:

    • 谢谢。仅供参考,我可以通过将强制转换为 ChurchNumeralT 替换为强制转换为 ChurchNumeralT 来从 @SuppressWarnings 中删除“原始类型”。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-05
    • 1970-01-01
    • 2017-02-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多