Java 8函数样式编程currying和Functions Composition之间的区别是什么

时间:2017-07-30 09:31:06

标签: java lambda functional-programming java-8 currying

我对函数式编程世界很陌生。最近我了解了关于currying和方法组合的新功能样式编程。理解使用java的函数式编程的真正本质是非常困难的,现在我有几个问题,但是,在提出所有这些问题之前,我在python上尝试了同样的东西,现在对一些核心概念有点熟悉。

1.在java中如何Currying和方法组成不同实际上我没有看到任何差异,特别是在阅读本文之后https://dzone.com/articles/higher-order-functions

2.作为程序员(从我的java编程角度来看)为什么我更喜欢currying。例如,为什么我会这样做 f(x){ return g(y) }代替f(x,y){ return x(y)}它有什么不同?

3 个答案:

答案 0 :(得分:8)

虽然两个操作都输出了一个函数,但这个例子使得差异非常明显:

  1. Currying采用单个函数f()并生成一个与f'()相同的“中间”函数f(),但某些参数已经修复。当您最终填写其余参数时,您将评估原始f()
  2. 组合将采用两个函数f()g(),并创建一个完全不同的函数g(f())
  3. 举个简单示例:f(x,y) = x+y,其中xy是整数。此函数的任何数量和currying组合都不会导致函数返回非整数结果。但是使用g(x) = x/2撰写它,然后得到g(f(x,y)) = (x+y)/2,这当然会很高兴地返回非整数。

    为什么你会使用currying?

    例如,Java实例方法是一个非常相似的过程的结果。实例方法与静态方法的不同之处在于它们有一个额外的隐藏参数this。当您说new Foo()时,基本上您将此隐藏参数绑定到新创建的Foo对象。因此,您不必调用函数void bar(Foo this, int x),而只需将其称为void bar(int x),第一个参数已经固定。 (顺便说一下,void bar(Foo this, int x)实际上是完全有效的Java语法,我们几乎从不使用它。)

    这并非完全是巧合,因为纯函数式语言只能有输出依赖于其输入的函数(与OO语言相反,其中方法的输出也可以取决于对象的内部状态。方法属于。)

    作为一般建议,如果你想学习函数式编程的本质,最好不要用Java来实现。甚至不是来自Scala。尝试从像Haskell这样的纯函数语言中学习它,然后你可以回到Java并更好地理解在其中实现的FP子集以及如何实现。

答案 1 :(得分:6)

我想通过@biziclop添加一些代码给非常好的解释:

功能Java中的currying示例:

BiFunction<Integer, Integer, IntFunction<Integer>> currying = (x, y) -> z -> x * y / z;
    System.out.println(currying.apply(5, 6).apply(2)); // 15

正如您所见,lambda已参数化。在这个例子中,我们假设将5乘以6,然后除以2.

首先调用apply(5),变量x获取值5,函数变为5 * y / z

然后apply(6)被调用,变量'y'获得值'6',函数变为5 * 6 / z

然后调用apply(2),变量'z'得到值'2',函数变为5 * 6 / 2

因为你可以使用currying这种方式在Java中几乎没用。 Currying在纯函数式语言中很有用,其中函数仅限于单个参数,并且它们受益于currying,它会转换带有多个参数的函数,因此可以使用单个参数调用多次调用它。

那么你如何从Java中的currying中受益?

当您需要在多个级别参数化函数时,它非常有用。例如,假设我们有几个集合,每个集合代表不同的类别,我们希望从每个类别中检索特定元素。下面是一个简单的示例,给出两个代表拼写数字的集合,分类为onestens。 示例:

public class Currying {

    private static List<String> ones = 
           Arrays.asList("Zero", "One", "Two", "Three", "Four", 
                                 "Five", "Six", "Seven", "Eight", "Nine");
    private static List<String> tens =
           Arrays.asList("Zero", "Ten", "Twenty", "Thirty", "Forty",
                                "Fifty", "Sixty", "Seventy", "Eighty", "Ninety");

    public static Function<String, Function<Integer, String>> getNumbers() {
        return units -> number -> {
                        return units == "Ones" ? ones.get(number % 10) 
                                               : tens.get(number % 10);
                                  };
    }

    public static void main(String[] args) {
        Function<String, Function<Integer, String>> currying = getNumbers();
        System.out.println(currying.apply("Tens").apply(8)); // 80
        System.out.println(currying.apply("Ones").apply(2)); // 2
    }

}

在上面的例子中,函数currying返回另一个函数     currying.apply("Ones").apply(2));

调用第一个apply("Tens"),变量units变为Tens

然后调用apply(2),变量number变为880集合中检索tens

同样的逻辑适用于currying.apply("Ones").apply(2))

答案 2 :(得分:4)

Currying是一种通过&#34;烘焙&#34;来创建新功能的方法。现有函数的参数。这通常在像Haskell这样的语言中完成,语言语法倾向于轻松地完成它。

一个典型的例子是让一个函数(addTwoNumbers a b)增加两个数字,其中currying是提供 less 参数来获取一个函数,该函数接受剩下的参数来做事。例如(addTwoNumbers 42)其中a是(42)但不是b,是一个函数(不是结果),它接受一个参数(b)并返回42 + b。所以((addTwoNumbers 42) 10)将返回52。

正如您所看到的,语言语法必须有助于此工作,并且Java没有多大帮助,这就是为什么它在教程中没有显示出来的原因。 Java 8中的功能方面主要是为了避免使用Streams的代码中的for循环,并且使用合理数量的预定义函数作为具有lambda表达式的脚手架。他们在Streams中进行了懒惰的评估,这是非常好的,也是一项了不起的成就,但是在代码的表现力方面并没有给程序员带来很多好处。

有关更多技术说明,请参阅https://wiki.haskell.org/Currying