zoukankan      html  css  js  c++  java
  • 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中终于实现了类似的功能-函数式接口
    为什么要用函数式接口?
    函数式接口可以让程序更好的组织和被理解。
    (图来自阮一峰 http://www.ruanyifeng.com/blog/2015/07/monad.html,表示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;
           }
  • 相关阅读:
    英文字典。怎样设计数据结构
    最近看的几部电影电视剧
    pylucene 中文
    提高浏览体验的五十个最佳FireFox扩展插件
    结构和细节
    <传> 《程序猿装B指南》,程序员童鞋们请认真学习
    c++ builder TTreeView customSort 实现 自定义排序
    《转》c++ 字符串系列:字符编码进阶(下)
    庆祝我又读完一本书
    c++ 回调函数深究
  • 原文地址:https://www.cnblogs.com/wanglao/p/6506959.html
Copyright © 2011-2022 走看看