zoukankan      html  css  js  c++  java
  • 词法作用域 vs 动态作用域

    词法作用域 vs 动态作用域

    链接:https://www.jianshu.com/p/cdebb5965000

    scheme是一门采用词法作用域(lexical scoping)的lisp方言,这个设计是从alogol语言里借鉴过来的。现在,词法作用域已经被许多lisp方言所吸收,实践表明,这的确是一项正确的设计,避免了很多奇怪的错误,比较符合人类的思维习惯。

    但是,在某些场合下,动态作用域又是很有用的特性,比如emacs lisp里面就默认采用动态作用域。

    下面的程序演示了词法作用域与动态作用域的不同

    (define x 1)
    (define y (lambda () x))
    (let ([x 2]) (y))

    如果是词法作用域,返回1,如果是动态作用域,返回2。

    在scheme中模拟动态作用域

    从一个简单的例子出发,演示如何在scheme里面实现动态作用域,我们想要写一个try catch宏来处理程序中的异常。其中一个函数叫做current-exception-handler,我们希望它是动态的,随代码运行位置而变化,永远指向当前的异常处理器,下面是我们写的第一个版本

    (define current-exception-handler
      (lambda (msg) (error "No Top Level Try")))
    (define-syntax try
      (syntax-rules (catch)
        [(_ expr ... (catch msg expr* ...))
            (call/cc (lambda (k)
                    (let ([msg (call/cc (lambda (k1)
                                          (set! current-exception-handler k1)
                                          (let ([result (begin expr ...)])
                                          (k result)
                                          )))])
                      expr* ...)))]))
    (define (throw msg) (current-exception-handler msg))

    其中,涉及throw的代码必须被包含在try里面,否则会导致错误。先来测试一下

    (try 1
         (throw 'foo)
         (catch msg (display "catch ") (display msg)))

    但是如果我们在后面再加上一行代码

    (throw 'test)

    这里就出现问题了,按照我们上面的要求throw应该在try catch块里面使用,而这里却不会报错,说明我们上面的代码错了。

    进入try catch块时,我们把current-exception-handler设置为当前try catch块的exception-handler,但当运行出try catch块的时候,exception-handler并没有发生变化,我们希望恢复原来的exception-handler,使得不论是正常退出或者是发生错误退出都能恢复原有的exception-handler,所以修改一下代码,就是:

    (define current-exception-handler
      (lambda (msg) (error "No Top Level Try")))
    (define-syntax try
      (syntax-rules (catch)
        [(_ expr ... (catch msg expr* ...))
            (call/cc (lambda (k)
                    (let ([msg (call/cc (let ([preserved current-exception-handler])
                                            (lambda (k1)
                                          (set! current-exception-handler (lambda (msg)
                                                                          (set! current-exception-handler preserved) 
                                                                            (k1 msg)))
                                          (let ([result (begin expr ...)])
                                            (set! current-exception-handler preserved)
                                          (k result)
                                          ))))])
                      expr* ...)))]))
    (define (throw msg) (current-exception-handler msg))

    这样,只要代码出了try catch块,current-exception-handler就会恢复成原来的,从而实现了动态作用域的效果。

    fluid-let语句

    如果你记得fluid-let语句的话,你就会发现上面的代码效果和fluid-let语句很类似,没错,fluid-let语句就是被设计用来实现dynamic scoping效果的,fluid-let语句的定义如下(摘自chez scheme user guide 8):

    (define-syntax fluid-let
       (lambda (x)
         (syntax-case x ()
           [(_ () b1 b2 ...) #'(let () b1 b2 ...)]
           [(_ ((x e) ...) b1 b2 ...)
            (andmap identifier? #'(x ...))
            (with-syntax ([(y ...) (generate-temporaries #'(x ...))])
              #'(let ([y e] ...)
                  (let ([swap (lambda ()
                                (let ([t x]) (set! x y) (set! y t))
                                ...)])
                    (dynamic-wind swap (lambda () b1 b2 ...) swap))))])))

    具体的实现细节就不说了,给一个例子:

    (define x 1)
    (define y (lambda () x))
    (fluid-let ([x 2]) (y))

    这和第一段代码一模一样,只不过用了fluid-let语句,就在scheme里面模拟出了dynamic scoping的效果。

    这样,try catch宏就可以写的很简单了:

    (define-syntax try
      (syntax-rules (catch)
        [(_ expr ... (catch msg expr* ...))
            (call/cc (lambda (k)
                    (let ([msg (call/cc (lambda (k1)
                                          (fluid-let ([current-exception-handler k1])
                                          (let ([result (begin expr ...)])
                                          (k result)
                                          ))))])
                      expr* ...)))]))

    parameterize语句-更好的选择

    但是出于某些原因(效率,赋值,标准库里面没有提供。。),我们并不想用fluid-let语句来解决问题,恰好rnrs标准库里面就提供了一个类似的,parameterize语句,同样可以完成任务。

    (define x (make-parameter 1))
    (define y (lambda () (x)))
    (parameterize ([x 2]) (y))

    如果我们在parameterize块外面尝试调用y函数,就会得到1,这说明parameterize只对块内生效,一旦出了parameterize块(不论是通过何种方式),就会恢复成原来的样子。

    scheme与其他lisp方言在作用域方面还有一个不同点,它的宏也是采用了词法作用域,而其他lisp方言的宏几乎都是动态作用域。如果你想了解scheme的宏如何实现动态作用域的效果,请看这里:http://schemeworkshop.org/2011/slides/Barzilay2011.pdf

    =============== End

  • 相关阅读:
    转:SVN Eclipse插件Subclipse安装和配置
    Apache、php、mysql单独安装配置
    HDU 1150:Machine Schedule(二分匹配,匈牙利算法)
    Oracle 数据的导入和导出(SID service.msc)
    swift-数组array
    wxWidgets刚開始学习的人导引(4)——wxWidgets学习资料及利用方法指导
    用php 把数组中偶数,选择出来
    java 异常 之 实战篇(trows 和 try catch Dead Code)
    语言处理程序
    使用Maven构建和部署J2EE应用程序的EAR文件
  • 原文地址:https://www.cnblogs.com/lsgxeva/p/10159153.html
Copyright © 2011-2022 走看看