java函数式编程--柯里化(Currying),闭包

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

java函数式编程--柯⾥化(Currying),闭包
近年来函数式编程趋热,在积累了⼀定的经验后,我也尝试着⽤函数式编程的思想来重新理解java编程。

闭包
闭包在Js中作为Js的⼊门概念,指的是函数的执⾏环境依赖于创建时的⼀系列作⽤域链的现象。

var v="a";
var fn=(function(){
v="b";
return function(){
v="c";
console.log(v);
}
})();
fn();
当我们分别注释v的“c”,“b”的赋值时,v的值将会向外寻找,对应的值也是“c”,“b”,“a”
也就是说fn带⾛了当时的数值作⽤域。

由于闭包的这⼀特性,我们得以访问函数内部变量。

⽽在Java中,我们也可以找到类似的功能:
public class Closure {
public String version="";
public static void main(String[] args) {
Closure c1=new Closure();
c1.version="1.0";
Moment m1=c1.getMoment();
System.out.println(m1.getVersion());
c1.version="2.0";
Moment m2=c1.getMoment();
System.out.println(m2.getVersion());
}
public Moment getMoment(){
return new Moment() {
@Override
public String getVersion() {
return version;
}
};
}
}
interface Moment{
String getVersion();
}
以上分别输出1.0 2.0,说明m1,m2记住了当时的状态,也就是说每次声明Moment对象的时候,编译器会把相关的值拷贝副本,放到对象的私有变量⾥。

利⽤这个特性,我们可以实现⼀些功能:⽐如⼀个创建成本很⾼的对象,需要输出⼀些包涵⾃⼰属性的⼀些特定对象,这时可以通过闭包这种⽅式⽅便的创建所多特定对象,⽽且每个特定对象可以保持⾃⼰的⼀些独⽴属性。

柯⾥化
在使⽤JavaScript的时候,有时会使⽤科⾥化,百度百科的释义:
在计算机科学中,柯⾥化(Currying)是把接受多个参数的函数变换成接受⼀个单⼀参数(最初函数的第⼀个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

这个技术由 Christopher Strachey 以逻辑学家 Haskell Curry 命名的。

不同于Functor,我认为科⾥化更侧重于函数的转换和组合。

我们可以使⽤科⾥化的概念包装⼀些函数
var calc= function(num){
return function(y){
return num + y;
}
}
var c1= calc (1);
var c2= calc (-1);
其中c1和c2其实是两个不同的函数,通过这种⽅式我们合理的组合函数
calc(10)(1) 11
calc(0.1)(1) 1.1
⽽JavaScript动态类型的特点⼜是得我们可以进⾏更⾼级的变换,如:
函数组合
var calc= function(numHandler){
return function(y){
return numHandler( y)+1;
}
}
特定的组合可以简化步骤:
function square(){}
function add(){}
function dvid(){}
function map(handler,list){
}
map(add,list);
map(dvid,list);
map(square,list);
柯⾥化之后
var h=handler(add,dvid,square)
map(h,list)
还可以延迟计算:
var curry = function(fn) {
var _args = []
return function cb() {
if (arguments.length == 0) {
return fn.apply(this, _args)
}
Array.prototype.push.apply(_args, arguments);
return cb;
}
}
var sum=function(){
var s=0;
for (var i = 0;i<arguments.length;i++) {
s += arguments[i];
}
return s;
}
var sumc=curry(sum);
sumc(1)(2)(3)();
java中缺少动态类型,⼜缺少类似于Prototype的原型,我们只能通过⼀些其他特性来弥补。

JDK7种我会使⽤接⼝和匿名函数来实现动态类和⽅法,JDK8当然是使⽤Function接⼝。

先来说JDK7. JDK7 java实现函数组合。

public class keli {
public calculator changeCalc(final initialize init, final int num) {
return new calculator() {
@Override
public int cal(int addend) {
return init.init(num) + addend;
}
};
}
public static void main(String[] args) {
initialize init1 = new initialize() {
@Override
public int init(int num) {
num = num * 10;
return num;
}
};
initialize init2 = new init2();
calculator clac1=new keli().changeCalc(init1,3);
calculator clac2=new keli().changeCalc(init2,3);
System.out.println(clac1.cal(2));
System.out.println(clac2.cal(2));
}
}
class init2 implements initialize{
@Override
public int init(int num) {
num = num / 10;
return num;
}
}
interface calculator {
int cal(int num);
}
interface initialize {
int init(int num);
}
注意:在JDK7以下需要加final关键字来固定住函数参数(JDK8不⽤加,编译器内部帮我们加了)。

上⾯我们通过将匿名函数作为参数传⼊构造了两个新的函数clac1
和 clac2,他们分别拥有不同的作⽤域。

在实际中⼀般明确的业务不会这样写,但是处理动态的流程,⽐如⾃定义的计算公式,⼀些插件会⽤到。

JDK8中的默认⽅法
jdk8实现了默认⽅法,简⽽⾔之就是在街⼝中添加default关键字,给继承类添加⽅法,这其实是⼀个妥协的⽅案,为了给原有类型增加功能,⼜不破坏⼤的结构。

类似⽅式有在C#中我们看到的扩展⽅法。

今天我们借默认⽅法实现⼀些类似Prototype的功能
public class currying3 implements Proto {
public List<String> paramList = null;
public currying3(List<String> paramList) {
this.paramList = paramList;
}
@Override
public List<String> getList() {
return this.paramList;
}
public static void main(String[] args) {
List<String> paramList = new ArrayList<String>();
Proto p = new currying3(paramList);
//延迟计算结果,当参数为空时计算
p.handle("aa").handle("bb").handle("cc").handle();
System.out.println("end.....");
List<String> paramList1 = new ArrayList<String>();
Proto p1 = new currying3(paramList1);
p1.handle("dd").handle("ee").handle("ff").handle();
System.out.println("end.....");
}
}
interface Proto {
public List<String> getList();
default Proto handle(String... param) {
List<String> paramList=this.getList();
if (param == null || param.length <= 0) {
for (String Str : paramList) {
System.out.println(Str);
}
return null;
} else {
for (String Str : param) {
paramList.add(Str);
}
return this;
}
}
}
输出结果:
aa
bb
cc
end.....
dd
ee
ff
end.....
类似于链式编程的写法,但是延迟执⾏。

当handle参数为空的时候,计算结果,这种⽅式可以⽤在可变数量的统计,延迟计算,查询中(不同于Hibernate的延迟查询,Hibernate通过代理类,声明时给予空属性,真正属性求值时在调⽤查询⽅法)。

函数式接⼝
在动态语⾔中,我可以使⽤⼀个var来表⽰function,但是java作为动态语⾔不允许这样做,只能使⽤接⼝作为参数,然后笨拙的使⽤接⼝中的⽅法。

早先C#中实现了委托作为⽅法指针,现在java8中终于实现了类似的功能-函数式接⼝
为什么要⽤函数式接⼝?
函数式接⼝可以让程序更好的组织和被理解。

(图来⾃阮⼀峰,表⽰Functor的概念)
我⽤这幅图来描述,对指定类型,进⾏指定的运算,这⼀步骤
它将函数作为参数传递,处理指定的值,它也可以⽤作科⾥化来组合函数
如⼀个这样的⽅法
static int handlerNum(Function<Integer,Integer>function,Function<Integer,Integer>function2,int num){
int result=function.apply(num);
result=function2.apply(result);
return result;
}。

相关文档
最新文档