多语言理解Closure

Closure概念

闭包是指可以访问另一个函数作用域变量的函数,可作为参数传递,一般是定义在外层函数中的内层函数。
Closure和 Lambda 是函数式编程语言中比较经典的语法。

作用:
1.同样可作为参数的'first class'特性
2.数据访问传递和数据隐藏,可以读取函数内部的变量
3.让函数内部变量的值始终保持在内存中。

Closure in JS

JavaScript支持 first class functions 和 lambdas, 但从immutability, algebraic data types, pattern matching, partial application等特性上看,它并不是严格意义上的Functional Language。

var getNum;//------------------------  
function getCounter() { // ----------2  
    var n = 1; 
    var inner = function () { return n++; } //-----0x1011
    return inner;
}

getNum = getCounter();//------------0x1011  
console.log(getNum()); //1 ---------4  
console.log(getNum()); //2 ---------5  

Closure in Java

Java 作为面向对向语言,没有普遍限定方法的单入单出、值不变性等规则,包括内存模型的设计和值传递的规则,不可能成为函数式语言,但是 java8中通过增加 Lambdas 表达式和函数式方法从语法层面来支持函数式的部分特性。

Java的Lambdas表达式提供的两个特性让语法做到了Closure.
1. 更简洁的函数式创建语法。
2. 通过 ByteCode 层面的封装,在运行时能过传递的参数创建方法。

    @FunctionalInterface // optional
    public interface NumToTextConverter {
        String convert(int x);
    }

    static void closureType1Demo() {
        NumToTextConverter textOfWeekday = new NumToTextConverter() {
            String [] weeks = { "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
            @Override
            public String convert(int num) {
                return (num > 0 && num <= weeks.length) ? weeks[num-1] : null;
            };
        };
        System.out.println(textOfWeekday.convert(1)); // Mon
    }

函数式:

    static Function<Integer, String> getTextOfWeekday(String [] weeks) {
        return num -> (num > 0 && num <= weeks.length) ? weeks[num-1] : null;
    }
    static void closureType5Demo() {
        Function<Integer, String> getArabTextOfWeekday = getTextOfWeekday(
            new String[]{ "Fri", "Sat", "Sun", "Mon", "Tue", "Wed", "Thu"}
        );
        Function<Integer, String> getIndianTextOfWeekday = getTextOfWeekday(
            new String[]{ "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"}
        );
        System.out.println(getArabTextOfWeekday.apply(5)); // Tue
        System.out.println(getIndianTextOfWeekday.apply(5)); // Fri
    }

Closure in Python

def f(x):  
    def g(y):
        return x + y
    return g  # Return a closure.

def h(x):  
    return lambda y: x + y  # Return a closure.

# Assigning specific closures to variables.
a = f(1)  
b = h(1)

# Using the closures stored in variables.
assert a(5) == 6  
assert b(5) == 6

# Using closures without binding them to variables first.
assert f(1)(5) == 6  # f(1) is the closure.  
assert h(1)(5) == 6  # h(1) is the closure.  

Problem in non function Language

由于 Closure 是function language 的特性,也是为了解决值不变的问题,混合Closure特性,使用变量时也会有一些问题需要注意,比如:

# Closures.py

def make_fun():  
    # Outside the scope of the returned function:
    n = 0

    def func_to_return(arg):
        nonlocal n 
        # Python 默认 variable是 local的,静态代码检查要求变量初始化,所以这里逻辑只能是 nonlocal

        print(n, arg, end=": ")
        arg += 1
        n += arg
        return n

    return func_to_return

x = make_fun()  
y = make_fun()

for i in range(5):  
    print(x(i))

print("=" * 10)

for i in range(10, 15):  
    print(y(i))

""" 
Output:  
0 0: 1  
1 1: 3  
3 2: 6  
6 3: 10  
10 4: 15  
==========
0 10: 11  
11 11: 23  
23 12: 36  
36 13: 50  
50 14: 65  
"""

由于 n 是全局变量,在第二次使用y = make_fun()时会出现n 已经被改变的问题。

同样,在 Java8中也会有同样问题, 不过 Java8的处理是禁止使用外部变量local variables referenced from a lambda expression must be final or effectively final

// AreLambdasClosures.java
import java.util.function.*;

public class AreLambdasClosures {  
    public Function<Integer, Integer> make_fun() {
        // Outside the scope of the returned function:
        int n = 0;
        return arg -> {
            System.out.print(n + " " + arg + ": ");
            arg += 1;
            // n += arg; // Produces error message
            return n + arg;
        };
    }
    public void try_it() {
        Function<Integer, Integer>
            x = make_fun(),
            y = make_fun();
        for(int i = 0; i < 5; i++)
            System.out.println(x.apply(i));
        for(int i = 10; i < 15; i++)
            System.out.println(y.apply(i));
    }
    public static void main(String[] args) {
        new AreLambdasClosures().try_it();
    }
}
/* Output:
0 0: 1  
0 1: 2  
0 2: 3  
0 3: 4  
0 4: 5  
0 10: 11  
0 11: 12  
0 12: 13  
0 13: 14  
0 14: 15  
*/

由于 Java中控制的变量是值而不是引用,所以改变对象的属性不会报错。

// AreLambdasClosures2.java
import java.util.function.*;

class myInt {  
    int i = 0;
}

public class AreLambdasClosures2 {  
    public Consumer<Integer> make_fun2() {
        myInt n = new myInt();
        return arg -> n.i += arg;
    }
}

Are Java 8 Lambdas Closures
Wiki-Closure (computer programming)

张鹏宇

继续阅读此作者的更多文章