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

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

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

    × 目录 [1]词法 [2]动态 前面的话 大多数时候,我们对作用域产生混乱的主要原因是分不清楚应该按照函数位置的嵌套顺序,还是按照函数的调用顺序进行变量查找.再加上this机制的干扰,使得变量查找极 ...

  2. js之词法作用域与动态作用域

    事实上JavaScript并不具有动态作用域,它只有词法作用域,简单明了,但是this机制某种程度上很像动态作用域 词法作用域:是一套引擎如何寻找变量以及会在何处找到变量的规则,它是定义在词法阶段的作 ...

  3. JavaScript深入之词法作用域和动态作用域

    作用域 作用域是指程序源代码中定义变量的区域. 作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限. JavaScript 采用词法作用域,也就是静态作用域. 静态作用域与动态作用域 因 ...

  4. Javascript中的词法作用域、动态作用域、函数作用域和块作用域(四)

    一.js中的词法作用域和动态作用域      词法作用域也就是在词法阶段定义的作用域,也就是说词法作用域在代码书写时就已经确定了.       js中其实只有词法作用域,并没有动态作用域,this的执 ...

  5. 深入学习js之——词法作用域和动态作用域

    开篇 当我们在开始学习任何一门语言的时候,都会接触到变量的概念,变量的出现其实是为了解决一个问题,为的是存储某些值,进而,存储某些值的目的是为了在之后对这个值进行访问或者修改,正是这种存储和访问变量的 ...

  6. 【转】深入理解javascript作用域——词法作用域和动态作用域

    前面的话 大多数时候,我们对作用域产生混乱的主要原因是分不清楚应该按照函数位置的嵌套顺序,还是按照函数的调用顺序进行变量查找.再加上this机制的干扰,使得变量查找极易出错.这实际上是由两种作用域工作 ...

  7. 前端知识体系:JavaScript基础-作用域和闭包-词法作用域和动态作用域

    词法作用域和动态作用域 1.作用域: 作用域是指程序代码中定义变量的区域 JavaScript采用词法作用域,也就是静态作用域 2.词法作用域和动态作用域 因为JavaScript采用的是词法作用域, ...

  8. JavaScript深入之词法作用域和动态作用域(转载)

    作用域 作用域是指程序源代码中定义变量的区域. 作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限. JavaScript 采用词法作用域(lexical scoping),也就是静态作 ...

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

    开始之前 由于本人也是JavaScript初学者,记录学习经过,怕以后会忘记. 对于JavaScript 初学者来说,最难的不是代码部分,而是对很多书籍中的术语的理解,大多时候想要理解一段JavaSc ...

随机推荐

  1. day34

    今日内容: 1.开启子进程的两种方式 2.join方法 3.进程之间内存空间相互隔离 4.僵尸进程和孤儿进程 5.守护进程 6.互斥锁 7.进程对象其他相关的属性和方法 1.开启子进程的两种方式 方式 ...

  2. 20155308《网络对抗》Exp9 Web安全基础实践

    20155308<网络对抗>Exp9 Web安全基础实践 本实践的目标理解常用网络攻击技术的基本原理.Webgoat实践下相关实验. 基础问题回答 SQL注入攻击原理,如何防御? 原理:攻 ...

  3. 20155325 Exp7 网络欺诈防范

    实践内容(3.5分) 本实践的目标理解常用网络欺诈背后的原理,以提高防范意识,并提出具体防范方法.具体实践有 (1)简单应用SET工具建立冒名网站 (1分) (2)ettercap DNS spoof ...

  4. Linux日记Day3---Linux的文件属性与目录配置

    Linux最优秀的地方之一,就在于它的多用户.多任务环境.为了让用户具有较安全的管理机制,文件的权限管理是很重要的.Linux通常将文件的访问方式分为分为三个类别,分别是owner/group/oth ...

  5. POJ1094——拓扑排序和它的唯一性

    比较模板的topological-sort题,关键在于每个元素都严格存在唯一的大小关系,而一般的拓扑排序只给出一个可能解,这就需要每趟排序的过程中监视它是不是总坚持一条唯一的路径. 算法导论里面的拓扑 ...

  6. 自动化部署-Jenkins+SVN+MSBuild

    这篇文章主要介绍下使用Jenkins实现自动化部署 下载 https://jenkins.io/download/ 安装 按步骤安装即可,下载的是windows版本,安装完成后,会看到这样一个正在运行 ...

  7. Revit二次开发-根据视图阶段(Phase)创建房间

    最近开发业务中,有一个自动创建房间的功能,很自然的想到了Document.NewRooms2方法.但是当前功能的特殊之处在于,Revit项目视图是分阶段(Phase)的,不同阶段的房间是互相独立的. ...

  8. Docker-安装(CentOS7)

    1.安装需要的软件包:yum-util提供yum-config-manager功能 yum install -y yum-utils device-mapper-persistent-data lvm ...

  9. Codejam Qualification Round 2019

    本渣清明节 闲里偷忙 做了一下codejam 水平不出意外的在投稿之后一落千丈 后两题的hidden test竟然都挂了 A. Foregone Solution 水题,稍微判断一下特殊情况(比如10 ...

  10. 1083. List Grades (25)-简单的排序

    给定区间[L,R],给出在这区间之内的学生,并且按照他们的成绩非升序的顺序输出. #include <iostream> #include <cstdio> #include ...