我对函数式编程世界很陌生。最近我了解了关于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)}
它有什么不同?
答案 0 :(得分:8)
虽然两个操作都输出了一个函数,但这个例子使得差异非常明显:
f()
并生成一个与f'()
相同的“中间”函数f()
,但某些参数已经修复。当您最终填写其余参数时,您将评估原始f()
。f()
和g()
,并创建一个完全不同的函数g(f())
。举个简单示例:f(x,y) = x+y
,其中x
和y
是整数。此函数的任何数量和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中受益?
当您需要在多个级别参数化函数时,它非常有用。例如,假设我们有几个集合,每个集合代表不同的类别,我们希望从每个类别中检索特定元素。下面是一个简单的示例,给出两个代表拼写数字的集合,分类为ones
和tens
。
示例:
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
变为8
从80
集合中检索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。