About
RSS

Bit Focus


Hello Closure

Posted at 2011-02-26 15:01:32 | Updated at 2024-12-24 18:58:04

    C++0x 搞了 lambda 算子, Java 更耐不住寂寞, 直接上了 Clojure, 函数式或伪函数式编程大潮一波波地袭来, 我终于还是下定决心, 直接去学习来自远古时代的 Lisp.
    当然了, 既然都是图灵完全的, C++, Python 什么的, 跟 Lisp 的实际表达能力差不多. 不过呢, 就像之前我对 STL 里 std::for_each 的吐槽那样, 语言不给力, 要表达出想要的东西会很麻烦. 在动手写了一些 Lisp 代码之后我觉得, Lisp 在这方面不会成问题.
    问题倒是, Lisp 代码似乎很难读懂, 括号很容易看错, 而且脑子里面要有一台能容纳至少七八个调用栈帧的 Lisp 虚拟机, 吃力啊.
    我倒也不指望成为 Lisp 达人, 以后用 Lisp 写各种各样的煮咖啡程序什么的, 只是想换换脑子, 让我找找 “原来代码还能这样写” 的感觉.

    言归正传, 说说 Lisp 的闭包. 所谓闭包, 是是带有编译期绑定自由变量且是第一级对象的函数.
    不愧是 Wikipedia, 描述太精准了, 让人难以看明白. 下面先简介一下这几个术语.
    自由变量指的是不在函数参数或函数域中的变量 (全局变量不算), 比如嵌套函数中内层函数引用的外层函数的参数或局部变量时, 这样被引用的变量; 甚至没有压根儿没有声明的变量 (当然前提是编译器支持这样的行为).
    不过上面说的后一种情况, 根本没声明的变量, 就不满足编译期绑定这个条件了. 此外, 一些动态名字查找的行为也会导致这个规则失效.
    所以诸如 sin, cos 之类的数学库函数都不是闭包, 它们并不引用自由变量.
    而第一级对象这个约束条件则更多的是要求语言支持, 第一级对象要求对象能够被存入变量, 能被作为函数参数或函数返回值, 能被聚合进对象, 能够运行期构造, 并且任意取名都不会影响其行为, 这些条件全部满足. Java 语言就不能将函数存入变量, 或作为参数返回值; 而 C++ 在 C++0x 标准之前没有嵌套函数, 无法构造出引用除了全局变量之外自由变量的函数, 所以这些语言的函数都不是闭包.
    概念就这么多, 下面开始闭包的基本应用, 以 Scheme 为例, 解释器使用 Guile.

    这里是一个简单闭包的例子
(define (multiple-by n)
        (define (multiple x) (* x n))
        multiple
)
    这里嵌套在内部定义的 multiple 就是闭包原型, 它引用了定义在 multiple-by 中的参数 n 作为自由变量. 调用 multiple-by 则可以得到一个闭包实例, 如
(define multiple-by-3 (multiple-by 3))
那么再
(multiple-by-3 4)
就能得到 12.

    Lisp 支持 lambda 算子语法来简化函数定义, 同时减去为函数起名字的麻烦, 如上例可以改成
(define (multiple-by n)
        (lambda (x) (* x n))
)
    闭包能引用自由变量的特性是很奇妙的, 把上面的例子改成下面这样
(define (multiple-by n)
        (lambda (x) n)
)
虽然这个时候它已经完全名不副实了, 调用 multiple-by 产生的闭包再被调用时只会傻乎乎地一个劲地返回相同的值.

    这倒是一个另类的闭包, 它的实际作用不是运算, 而是数据运载. 也就是说, 闭包的作用不一定是执行某种操作, 而可以是存放数据的对象. 比如下面这个例子, 就利用闭包构造了一个 pair
(define (make-pair x y)
        (lambda (n) (if (= n 0) x
                                y
                    )
        )
)
调用 make-pair 得到的闭包确实行为会类似一个 pair, 传入 0 它返回 pair 的第一个元素, 传入其它值则返回后一个元素.

    这样做甚至还能构造有随机索引行为的 list, 如
(define (make-list . elements)
        (define (at n sub-list)
                (if (= n 0) (car sub-list)
                            (at (- n 1) (cdr sub-list))
                )
        )
        (lambda (n) (at n elements))
)
    最后, 再来一个 SICP 上练习中给出的 (很绕的) pair 实现
(define (cons x y) (lambda (g) (g x y)))
(define (first pair) (pair (lambda (f s) f)))
(define (second pair) (pair (lambda (f s) s)))
那么, (first (cons x y)) 将会得到第一个元素 x, 而 (second (cons x y)) 将会得到第二个元素 y.

Post tags:   Closure  Lisp

Leave a comment:




Creative Commons License Your comment will be licensed under
CC-NC-ND 3.0


. Back to Bit Focus
NijiPress - Copyright (C) Neuron Teckid @ Bit Focus
About this site