美文网首页
延迟计算和闭包

延迟计算和闭包

作者: 当年的反应慢 | 来源:发表于2017-10-11 18:26 被阅读0次

    前言

    一直觉得函数式编程中的闭包和延迟计算是很神奇的技术,因为一直不知道原理,所以也不知道如何用好他们。看过几遍介绍,但终究是没有摸到什么头脑,直到一个偶然的机会,突然明白了...

    一个延迟计算的例子

    List<String> stringList=Arrays.asList("abc","cde","efg","ghi","ijk");
    stringList.stream().map(s->s.toUpperCase()).peek(System.out::println).collect(Collectors.toList());
    

    这是一个Java8中运用stream计算的一个例子,意思是把stringList中的所有字符串转换成大写的,然后输出出来,然后放到新的List中
      有意思的是,如果代码写成这样

    List<String> stringList=Arrays.asList("abc","cde","efg","ghi","ijk");
    stringList.stream().map(s->s.toUpperCase()).peek(System.out::println);
    

    它是不会进行System.out.println()操作的。而如果写成这样

    List<String> stringList=Arrays.asList("abc","cde","efg","ghi","ijk");
    Stream<String> stream= stringList.stream().map(s->s.toUpperCase());
    

    得到的stream里的字符串流还是小写的,这就是所谓的延迟计算。
      其实我在这里挺讨厌延迟计算的,之前很不明白为什么不能直截了当的给我计算结果,而需要进行终结操作,事实上终结操作并不是我想要的,只是为了应对延迟计算不得已做的操作。这个问题先留在这,下面我们先看下闭包,因为这两个技术的原理是都来自高阶函数。

    闭包

    Java闭包的用法

    public class FirstLambdaExpression {  
        public String variable = "Class Level Variable";  
        public static void main(String[] arg) {  
            new FirstLambdaExpression().lambdaExpression();  
        }  
        public void lambdaExpression(){  
            String variable = "Method Local Variable";  
            String nonFinalVariable = "This is non final variable";  
            new Thread (() -> {  
                //Below line gives compilation error  
                //String variable = "Run Method Variable"  
                System.out.println("->" + variable);  
                System.out.println("->" + this.variable);  
           }).start();  
        }  
    } 
    

    这是java8中的一个闭包的例子,用这个例子的主要目的就是演示下Java也可以用闭包,为什么使用闭包,一言以蔽之,就是为了在链式计算中维持一个上下文,同时进行变量隔离,这么说有点抽象,再举个例子

    List<String> stringList=Arrays.asList("abc","cde","efg","ghi","ijk");
    stringList.stream().reduce((s1,s2)->s1+s2).get();
    

    输出: <code>abccdeefgghiijk</code>
       reduce()接收有两个参数的函数,它的作用是把上一次计算的结果作为第一个参数,然后把这次要计算的量作为第二个参数,然后进行计算。
    如果不使用闭包呢,我们将会得到下面的代码

    List<String> stringList=Arrays.asList("abc","cde","efg","ghi","ijk");
            //System.out.println( stringList.stream().reduce((s1,s2)->s1+s2).get());
    String temp="";
    for(String s: stringList){
        temp+=s;
        }
    System.out.println(temp);
    

    我们需要一个中间变量来维持这个计算能进行下去。好吧,我承认这没有什么不可以接受的,我们之前就一直这样写。但是如果变成这样了呢

    List<String> stringList1=Arrays.asList("abc","cde","efg","ghi","ijk");
    List<String> stringList2=Arrays.asList("abc","cde","efg","ghi","ijk");
    //System.out.println( stringList.stream().reduce((s1,s2)->s1+s2).get());
    String temp="";
    for(String s: stringList1){
        temp+=s;
        }
    String temp1="";
    for(String s: stringList2){
        temp+=s;
    }
    
     System.out.println(temp);
     System.out.println(temp1);
    

    对应是使用闭包的写法

    List<String> stringList1=Arrays.asList("abc","cde","efg","ghi","ijk");
    List<String> stringList2=Arrays.asList("abc","cde","efg","ghi","ijk");
    System.out.println( stringList1.stream().reduce((s1,s2)->s1+s2).get());
    System.out.println( stringList2.stream().reduce((s1,s2)->s1+s2).get());
    

    从这个例子中我们看到了使用中间变量的不便性,对java来说这个中间变量一般在方法里面,不会有多大影响,但是对应javascript来说,太容易造成变量污染了,尤其是你用完这个字符串忘掉置空或者使用前忘记置空了,这就是为什么闭包的特性在javascript中是与生俱来的,而在java中直到第八个版本才出现的原因了(开玩笑的。JavaScript是从一开始就是一种可以进行函数式编程的语言,java第八版本才开始变得可以进行函数式编程,闭包是函数式编程语言必须提供的一种特性,正如例子中的reduce()函数一样,能够接收函数作为参数的语言,必然也天生的实现了闭包)。

    好了到目前为止我们已经对闭包和延迟计算有了一点点了解,那接下来我们就要探究下其实现原理了。在这我们先介绍一个概念高阶函数

    高阶函数

    定义

    在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:

    1. 接受一个或多个函数作为输入
    2. 输出一个函数
         在数学中它们也叫做算子(运算符)或泛函。微积分中的导数就是常见的例子,因为它映射一个函数到另一个函数。
         在无类型 lambda 演算,所有函数都是高阶的;在有类型 lambda 演算(大多数函数式编程语言都从中演化而来)中,高阶函数一般是那些函数型别包含多于一个箭头的函数。在函数式编程中,返回另一个函数的高阶函数被称为Curry化的函数。
         在很多函数式编程语言中能找到的 map 函数是高阶函数的一个例子。它接受一个函数 f 作为参数,并返回接受一个列表并应用 f 到它的每个元素的一个函数。

    范例

    这是一个javascript 的例子, 其中函式 g() 有一引数以及回传一函数. 这个例子会打印 100 ( g(f,7)= (7+3)×(7+3) ).

    function f(x){
        return x + 3
    }
       
    function g(a, x){
        return a(x) * a(x)
    }
    console.log(g(f, 7))
    

    这是接收一个函数作为参数的例子,下面我们以一个返回一个函数的例子

    function outer(){
        var a=1;
        var inner= function(){
        return a++;
        }
        return inner
    }
    var b=outer();
    console.log(b());
    console.log(b());
    

    分析

    我们从javascript语言入手进行分析是因为java语言没有办法定义高阶函数,高阶函数是延迟计算和闭包的来源。顺便提一句,高阶函数的设计原理也并没有多么复杂,以我了理解,高阶函数实现起来大概来源于C语言的指向函数的指针,指向函数的指针也来源于汇编语言,对于这么底层的语言来讲,没有函数的概念只有代码块的概念,在代码块间跳来跳去,就实现了函数,在这里我就不展开来说了。

    延迟计算

    我们拿上面的返回函数的例子来讲

    var b=outer();
    

    此时,b是个什么?b是一个函数,此时

    var b=function(){
        return a++;
    }
    

    在这里<b>b只是函数定义,并没有执行,而函数执行的地方在于 <key>console.log(b());</key></b>
    只用这一句话,就说明了 <em>延迟计算</em> 的实质,只定义不使用。
    所以回头来看下我们前面的Java代码里“延迟计算”,这里就比较明了了,map函数和reduce函数只是接收了函数,并没有立即执行,这就是为什么需要一步终结操作了。

    闭包

    提到闭包不得不提另一个口号,那就是“在函数式编程中,函数是编程语言中的一等公民”,每个函数都可以当做对象来使用,再举一个例子

    function a(){
        var i=1;
        return function () {
            return ++i;
        }
    }
    var b=a();
    
    console.log(b());//2
    
    var c=a();
    
    console.log(b());//3
    
    console.log(c());//2
    
    

    可以看出b和c是隔离开的,互相不影响的,这里我们可以类比成Java中的代码:

    class Outter{
    
        int i=1;
    
        public int inner(){
      
            return this.i++;
    
        }
    
    }
    Outter b=new Outer();
    Outter c=new Outer();
    System.out.println(b.inner());
    System.out.println(b.inner());
    System.out.println(c.inner());
    

    在javascript中的写法也可以写成:

    function Outter(){
        var i=1;
        var inner=function(){
            return i++;
        }
        return inner;
    }
    var b=new Outter();//实际上返回一个inner对象
    var c=new Outter();//实际上又返回一个inner对象
    console.log(b());//1
    console.log(b());//2
    console.log(c());//1
    console.log(c());//2
    

    ok,到这里,基本上就能理解闭包如何使用了,在我看来,闭包实际上是函数式编程的面向对象编程,或者函数式编程中面向对象的一种实现方式。反正我是这么理解了闭包的,自从这样想明白之后,我突然变得会使用闭包了
       以上

    相关文章

      网友评论

          本文标题:延迟计算和闭包

          本文链接:https://www.haomeiwen.com/subject/yftgyxtx.html