用纯函数式思维在Java8下写的一段奇葩程序
首先说一下什么是纯函数式。在我的理解,“纯函数式”用一句话就可以描述:Anything is value.——我的理解不一定准确,但我就是这么理解的。
就是所有的东西都是值——没有变量;包括函数在内都是值——是值,就可以传递(包括函数)。
为什么说这段程序是奇葩呢?
其一、传统的Java是面向对象的,自从Java8中加入了lambda,Java就变成了“面向对象”和“函数式”两种方式的混合语言。这段程序全部使用lambda的语法来写,与平常写的Java风格完全不同。
其二、在Java的对象中保存数据通常是用对象的属性,lambda表达式本质上仍然是对象,但它并没有属性,但我们却成功的在lambda中保存了数据,这相对于传统的Java编程思维也是一种跳跃。
其三、在Scheme中实现同样的函数(或lambda)非常简洁,也很容易读,而在Java中的实现,可读性好差,以致于我自己都快看不懂了,所以说是“奇葩”。
这段程序用两种方式实现了同样的功能:
1.实现一个函数cons,这个函数有两个参数x和y,并返回一个东西(这个东西以下简称为c)。
2.实现一个函数car,传入c,并返回原来传入cons中的x。
3.实现一个函数cdr,传入c,并返回原来传入cons中的y。
这实际上是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));
}
}
用纯函数式思维在Java8下写的一段奇葩程序的更多相关文章
- G++ 4.4.7 无法编译模板程序,Vs可以,和解?智者尾部留言,本人第一次使用vs pro,通常并且习惯在linux下写些小东西,虽然程序简单;
vs 模板编译运行Ok \ linux g++ 4.4.7编译模板测试程序,报无法定义 template <typename or class 中的 AnyType> 类型的数据 Exam ...
- Frege-基于JVM的类Haskell纯函数式编程语言
Frege是一门受Haskell语言启示而设计的纯函数式编程语言.Frege程序会被编译为Java,并执行于JVM上.它与Haskell是如此的类似.以至于有人称它为JVM上的Haskell.取Fre ...
- 使用Code::blocks在windows下写网络程序
使用Code::blocks在windows下写网络程序 作者 He YiJun – storysnail<at>gmail.com 团队 ls 版权 转载请保留本声明! 本文档包含的原创 ...
- Delphi在win7/vista下写注册表等需要管理员权限的解决方案
看到论坛好多人问win7下写注册表的问题,我结合自己的理解写了一点东西,首先声明一下,本人初学Delphi,水平有限,大家见笑了,有什么不对之处请老鸟多指点. [背景]win7/Vista提供的UAC ...
- cocos2dx c++ 在mac下写的中文凝视,在win32下编译时不通过
今天遇到个奇怪的问题,在mac下写的程序,加的中文凝视,编译没有问题,可是在win32下(使用的时vs2012, win7 64bit 系统)编译就总是报错 最后在中文凝视后 加一个空格,或者 换行, ...
- 怎样在Windows和Linux下写相同的代码
目前,Linux在国内受到了越来越多的业内人士和用户的青睐.相信在不久的将来,在国内为Linux开发 的应用软件将会有很大的增加(这不,金山正在招兵买马移植WPS呢).由于未来将会是Windows和L ...
- 纯CSS实现二级导航下拉菜单--css的简单应用
思想:使用css的display属性控制二级下拉菜单的显示与否.当鼠标移动到一级导航菜单的li标签时,显示二级导航菜单的ul标签.由于实现起来比较简单,所以在这里直接给出了参考代码. 1.纯CSS二级 ...
- a,b,c为3个整型变量,在不引入第四个变量的前提下写一个算法实现 a=b b=c c=a?(异或解决值互换问题)
package com.Summer_0424.cn; /** * @author Summer * a,b,c为3个整型变量,在不引入第四个变量的前提下写一个算法实现 a=b b=c c=a? */ ...
- a,b为2个整型变量,在不引入第三个变量的前提下写一个算法实现 a与b的值互换
package com.Summer_0424.cn; /** * @author Summer * a,b为2个整型变量,在不引入第三个变量的前提下写一个算法实现 a与b的值互换? */ publi ...
随机推荐
- 使用NPOI将DataTable生成Excel
听闻npoi 2.0版本支持excel2007格式了,表示期待其表现.不过目前还是使用1.2.5稳重点. 生活中有太多的列表都需要一个导出功能,当然这里的生活指的的程序员的生活.DataTable是从 ...
- server证书安装配置指南(Tomcat 6)
一. 生成证书请求 1. 安装JDK 安装Tomcat须要JDK支持. 假设您还没有JDK的安装.则能够參考 Java SE Development Kit (JDK) 下载. 下载地址: ...
- BIOS Setup
一般而言,普通的计算机系统应用不必关注BIOS的设置.但是如果涉及到主板集成声卡,网卡,或需要进行远程网络唤醒等操作时,必须在BIOS中设置相应参数才能使电脑正常工作.BIOS能对硬件设备进行初始 ...
- [TypeScript] Represent Non-Primitive Types with TypeScript’s object Type
ypeScript 2.2 introduced the object, a type that represents any non-primitive type. It can be used t ...
- 制作svg动画
要实现一步一步画出来一个图片,css3做不到吧.除非一张张的图片定时显示.想不到别的招了.如今用的是一个插件,做了一个svg动画. 插件地址:http://lazylinepainter.info/ ...
- Vue 实现原理
vue.js是数据驱动web界面的库.vue核心思想个:数据驱动.组件系统 vue实现数据驱动视图原理 数据驱动是vue最大特点,所谓的数据驱动就是:当数据发生变化的时候,界面会相应的变化,我们不需要 ...
- ubuntu下apache+mysql+php+mysql等之webserver搭建
相信非常多人跟我一样,想搭建一个自己的webserver.网上资料非常多.可是因为版本号的区别,总是存在依照一个教程来做无法全然实现的问题.近期我也折腾了好几天,google不能用,仅仅能百度,真想说 ...
- Tomcat 6.x Perm区内存泄露问题
Tomcat 6.x JSP文件最后改动时间大于当前系统时间导致Perm区内存泄露问题(java Memory pool CMS Perm Gen) 出现场景: 因为測试业务,须要模拟跨天測试,所以一 ...
- Paypal支付(一)MPL真正的快捷支付
一.前导 前面讲到了MEC支付,是在Web端集成好的,在手机端仅仅需通过WebView进行载入就可以,不须要不论什么Paypal第三方架包.以下将的是MPL支付.须要架包. 这样的支付的形式能够參考以 ...
- 在js中取选中的radio值
在js中取选中的radio值 <input type="radio" name="address" value="0" /> & ...