词法作用域 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

原文地址:https://www.cnblogs.com/lsgxeva/p/10159153.html

时间: 2024-10-11 06:38:47

词法作用域 vs 动态作用域的相关文章

深入理解javascript作用域系列第二篇——词法作用域和动态作用域

× 目录 [1]词法 [2]动态 前面的话 大多数时候,我们对作用域产生混乱的主要原因是分不清楚应该按照函数位置的嵌套顺序,还是按照函数的调用顺序进行变量查找.再加上this机制的干扰,使得变量查找极易出错.这实际上是由两种作用域工作模型导致的,作用域分为词法作用域和动态作用域,分清这两种作用域模型就能够对变量查找过程有清晰的认识.本文是深入理解javascript作用域系列第二篇——词法作用域和动态作用域 词法作用域 第一篇介绍过,编译器的第一个工作阶段叫作分词,就是把由字符组成的字符串分解成

深入作用域之静态作用域与动态作用域

概念: 静态作用域指的是一段代码,在它执行之前就已经确定了它的作用域,简单来说就是在执行之前就确定了它可以应用哪些地方的作用域(变量). 动态作用域在代码执行的时候才确定它的作用域的,以及作用域链. 静态作用域: var a = 10; function fn(){ var b = 1; console.log(a+b); } fn(); //11 在创建函数fn的时候就已经确定了它可以作用那些变量,如果函数fn里面有变量a就直接作用函数fn里面的变量a,如果没有就往上一级找,这就是静态作用域.

静态作用域和动态作用域

静态作用域指的是一段代码,在它执行之前就已经确定了它的作用域,简单来说就是在执行之前就确定了它可以应用哪些地方的作用域(变量). 动态作用域–函数的作用域是在函数调用的时候才决定的 JavaScript采用的是词法作用域即静态作用域: // 静态作用域: var a = 10; function fn() { var b = 1; console.log(a + b); } fn(); // 11 在创建fn函数时的时候就已经确定了它可以作用哪些变量,如果函数fn里面有变量a就直接操作变量a,

JavaScript 词法、静态、动态作用域初级理解

开始之前 由于本人也是JavaScript初学者,记录学习经过,怕以后会忘记. 对于JavaScript 初学者来说,最难的不是代码部分,而是对很多书籍中的术语的理解,大多时候想要理解一段JavaScript书籍中的解释内容,就会涉及很多术语,在术语不通的情况下都会对内容理解偏差(导致很难记住所学内容),由于本人在阅读书籍时经常会遇到这一问题,所以今天将记下这一基础内容. 一.需要关注点 1)执行环境(作用域)函数执行环境 变量作用域 2)函数作用域和声明提前 3)自由变量 4)词法作用域和静态

js 函数作用域, 块级作用域和词法作用域

函数作用域, 块级作用域和词法作用域 0 作用域: 0.1 作用域是程序源代码中定义变量的区域. 0.2 作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限. 0.3 ECMAScript6之前只有全局作用域和函数作用域. 0.4 JavaScript采用词法作用域(lexical scoping),也就是静态作用域. var scope = "global scope"; function checkscope(){ var scope = "local sc

区分词法作用域(js)与动态作用域(精!)

在js学习中,词法作用域是必须要掌握的! 在这里,我将总结一下<你不知道的JS>一书中词法作用域的重点并分享给大家! 首先带来一段代码示例: function foo(){ console.log(a);//2 } function bar(){ var a = 3; foo(); } var a = 2; bar(); js词法作用域输出结果:"2"; 动态作用域输出结果:"3" 下面我将对两种作用域的输出结果进行分析: #在对结果分析之前首先我们要了

动态作用域与this +apply和call +bind

词法作用域是一套关于引擎如何寻找变量以及会在何处找到变量的规则. (函数作用域和块作用域) JavaScript 中的作用域就是词法作用域,也就是静态作用域,由定义代码决定 动态作用域似乎暗示有很好的理由让作用域作为一个在运行时就被动态确定的形式,而不是在写代码时进行静态确定的形式 动态作用域并不关心函数和作用域是如何声明以及在何处声明的,只关心它们从何处调用.换句话说,作用域链是基于调用栈的,而不是代码中的作用域嵌套 ps:可以忽略下面这段话 : 静态类型语言是指在编译时变量的数据类型即可确定

《你不知道的javascript》一、函数作用域和块作用域

函数中的作用域 所谓函数作用域,就是属于这个函数的全部变量都可以在整个函数的范围内使用及复用. 1 function foo(a) { 2 var b=a; 3 function bar(c){ 4 var c=b*2; 5 console.log(c); 6 } 7 bar(); //4 8 } 9 foo(3); 1 function foo(a) { 2 var b=a; 3 function bar(c){ 4 var c=b*2; 5 console.log(c); 6 } 7 bar

python作用域和JavaScript作用域

JavaScript 一.JavaScript中无块级作用域 一个大括号一个作用域,就属于块级作用域,在Java和c#才存在块级作用域 function Main(){ if(1==1){ var name = 'seven'; } console.log(name); } // 输出: seven 二.JavaScript采用函数作用域 在JavaScript中每个函数作为一个作用域,在外部无法访问内部作用域中的变量 function Main(){ var innerValue = 'sev