首先说一下什么是纯函数式。在个人理解,“纯函数式”用一句话就能够描述:Anything is value.——个人理解不必定准确,但我就是这么理解的。java
就是全部的东西都是值——没有变量;包括函数在内都是值——是值,就能够传递(包括函数)。算法
为何说这段程序是奇葩呢?编程
其1、传统的Java是面向对象的,自从Java8中加入了lambda,Java就变成了“面向对象”和“函数式”两种方式的混合语言。这段程序所有使用lambda的语法来写,与日常写的Java风格彻底不一样。数据结构
其2、在Java的对象中保存数据一般是用对象的属性,lambda表达式本质上仍然是对象,但它并无属性,但咱们却成功的在lambda中保存了数据,这相对于传统的Java编程思惟也是一种跳跃。app
其3、在Scheme中实现一样的函数(或lambda)很是简洁,也很容易读,而在Java中的实现,可读性好差,以至于我本身都快看不懂了,因此说是“奇葩”。 函数
这段程序用两种方式实现了一样的功能:翻译
1.实现一个函数cons,这个函数有两个参数x和y,并返回一个东西(这个东西如下简称为c)。对象
2.实现一个函数car,传入c,并返回原来传入cons中的x。编译器
3.实现一个函数cdr,传入c,并返回原来传入cons中的y。io
这其实是Scheme中自带的“序偶”,不过即便Scheme语言自己的库不自带cons,咱们本身实现也是很简单的(下面的程序中,我在注释部分列出了Scheme的实现);Java骨子里是面向对象的基因,若是用面向对象的方式来实现上述功能是很是简单的,但用lambda的语法来实现就显得奇葩了。
下面先把奇葩贴出来,而后在后面的注释中解释一下:
import java.util.function.BiFunction;
import java.util.function.Function;
public class TestCons {
public static void main(String[] args) {
testCons1();
testCons2();
}
private static void testCons1() {
/*
(define (cons x y)
(lambda (m)
(cond ((= m 0) x)
(else y))))
(define (car z) (z 0))
(define (cdr z) (z 1))
上面几行Scheme代码翻译成Java是以下三行代码
*/
BiFunction<Object, Object, Function<Integer, Object>> cons = (x, y) -> m -> m == 0 ? x : y; // 注释1
Function<Function, Object> car = z -> z.apply(0); // 注释2
Function<Function, Object> cdr = z -> z.apply(1); // 注释3
Function c = cons.apply(3, "abc"); // 调用cons,并传入两个值,建立了对象c
System.out.println(car.apply(c)); // 从c中取出第一个值
System.out.println(cdr.apply(c)); // 从c中取出第二个值
}
private static void testCons2() {
/*
(define (cons x y)
(lambda (m) (m x y)))
(define (car z)
(z (lambda (p q) p)))
(define (cdr z)
(z (lambda (p q) q)))
上面几行Scheme代码翻译成Java是以下三行代码
*/
BiFunction<Object, Object, Function<BiFunction, Object>> cons = (x, y) -> f -> f.apply(x, y); // 注释4
Function<Function<BiFunction, Object>, Object> car = f -> f.apply((a, b) -> a); // 注释5
Function<Function<BiFunction, Object>, Object> cdr = f -> f.apply((a, b) -> b); // 注释6
Function c = cons.apply(3, "abc"); // 调用cons,并传入两个值,建立了对象c
System.out.println(car.apply(c)); // 从c中取出第一个值
System.out.println(cdr.apply(c)); // 从c中取出第二个值
}
}
注释1:此行建立一个叫cons的lambda表达式,此表达式有两个参数x和y,并返回另一个lambda,这个lambda有一个整数类型的参数m,且当m为0时,返回x,不然返回y。
注释2:此行建立一个叫car的lambda,此lambda有一个参数,且这个参数也是一个lambda(z),car的lambda体中是把0传入z中,并获得返回值。
结合“注释1”和“注释2”这两行,咱们能够这样解释:cons返回的lambda能够作为car的参数。
注释3:和“注释2”差很少,再也不缀述。
注释4:此行建立一个叫cons的lambda,此lambda有两个参数x和y,反返回另一个lambda,这个lambda有一个参数,且这个参数也是一个lambda(f),在cons返回值的lambda体中应用f,并把cons的两个参数作为f的两个参数——至关拗口——简单点说就是cons并不作什么,只是把x和y,交给一个lambda,而这个lambda也不作什么,只是等着另一个lambda(f)来处理x和y,而这个f要经过参数传过来。
注释5:此行建立一个小car的lambda,此lambda有一个参数(此参数可传入cons返回的lambda),从“注释4”中咱们知道cons返回的lambda还须要一个lambda作为参数来处理两个参数,因此咱们传入一个(a, b) –> a,这里在a和b中返回前者,这就是car的目的。
注释6:和“注释5”差很少,再也不缀述。
处处都是lambda,很难读,但在Scheme中彻底同样的算法实现就很简洁,可读性很好,这是为何呢?
我认为这是S表达式的语法结构造成的效果——S表达式是以数据结构的方式存储程序的,这样的状况下,假设Scheme中没有lambda,此时咱们要扩展编译器来支持lambda,则咱们不须要修改编译器的parser部分——但Java的lambda没有办法与现有的其它语法的结构同样,因此就只能新增新的语法结构了,但又要与原有的基因融合,这样虽然lambda在本质上仍然是对象,但在表现形式上与原有的Java却有很大的排异反应。
这不是一两句话能说得明白的,也有点扯远了。
下面再演示一个邱奇计数的例子,这个就不写注释了:
import java.util.function.Function;
public class testChurchNum {
public static void main(String[] args) {
Function<Function<Function<Object, Object>, Function>, Function<Function<Object, Object>, Function>>
add_1 = n -> f -> x -> f.apply(n.apply(f).apply(x));
Function<Function<Object, Object>, Function> zero = f -> x -> x;
Function<Function<Object, Object>, Function> one = add_1.apply(zero);
Function<Function<Object, Object>, Function> tow = add_1.apply(one);
Function<Function<Object, Object>, Function> one_1 = f -> x -> f.apply(x);
Function<Function<Object, Object>, Function> tow_1 = f -> x -> f.apply(f.apply(x));
Function f = x -> (((Integer) x) + 1); System.out.println(zero.apply(f).apply(0)); System.out.println(one.apply(f).apply(0)); System.out.println(one_1.apply(f).apply(0)); System.out.println(tow.apply(f).apply(0)); System.out.println(tow_1.apply(f).apply(0)); }}