The Racket Guide阅读笔记,chapter 15.
15 Reflection and Dynamic Evaluation
15.1 eval
eval可以接受动态生成的表达式。
(define (eval-formula formula)
(eval `(let ([x 2]
[y 3])
,formula)))
上述例子很简单,甚至不必需要用到eval解决,只需把函数作为值传递即可:
(define (apply-formula formula-proc)
(formula-proc 2 3))
racket可以使用dynamic-require
动态加载一个模块,其内部也是用eval来实现。
15.1.1 Local Scopes
eval对局部变量不敏感。
15.1.2 Namespaces
eval接受一个额外的参数作为环境:
#lang racket
(define ns (make-base-namespace))
(eval '(cons 1 2) ns) ; works
上面的例子中,环境由make-base-namespace产生的。如果不指定这个额外的参数,那么就由current-namespace
来决定当前的环境。在交互模式下,定义的绑定时可见的,非交互模式下,其默认为空。
15.1.3 Namespaces and Modules
通过module->namespace
可以模块产生一个环境:
> (module m racket/base
(define x 11))
> (require 'm)
> (define ns (module->namespace ''m))
> (eval 'x ns)
在模块内部,可能需要通过下面的手段将模块转为环境:
#lang racket
(define-namespace-anchor a)
(define ns (namespace-anchor->namespace a))
(define x 1)
(define y 2)
(eval '(cons x y) ns) ; produces (1 . 2)
15.2 Manipulating Namespaces
环境(又称scope或者namespace)封装了两种信息:
- 从标识符到绑定的映射
- 从模块名到模块声明以及实例
前者在顶层上下文使用。后者在dynamic-require的时候时使用。像(eval '(require racket/base))
的操作有可能两者兼用。
15.2.1 Creating and Installing Namespaces
通过make-empty-namespace
可以创建空的新环境。这个环境甚至无法用来执行(require racket)
,下面的代码会失败:
(parameterize ([current-namespace (make-empty-namespace)])
(namespace-require 'racket))
原因是找不到racket模块。
make-base-empty-namespace
同样创建一个空环境,只不过这个环境会附着到racket/base之上。
下面是一个例子:
(define (run-dsl file)
(parameterize ([current-namespace (make-base-empty-namespace)])
(namespace-require 'my-dsl)
(load file)))
注意,上面的例子使用的是naemspace-require,而不是eval。
15.2.2 Sharing Data and Code Across Namespaces
namespace-attach-module可以往环境中添砖加瓦。
在模块内部,define-namespace-anchor和namespace-anchor->empty-namespace可能是一个更可靠的做法。
15.3 Scripting Evaluation and Using load
lisp传统上没有模块系统,而使用load来加载代码。简单地说,load从文件读入代码,并eval之。
load并不支持指定环境为参数,所有需要使用parameterize:
#lang racket
(parameterize ([current-namespace (make-base-namespace)])
(load "here.rkts"))
或者
(define-namespace-anchor a)
(parameterize ([current-namespace (namespace-anchor->namespace a)])
(load "here.rkts"))
也可以直接指定脚本为#lang racket/load
。但是这会降低语法检查的水准。
15.4 Code Inspectors for Trusted and Untrusted Code
声明一个模块时,current-code-inspector
会关联到模块声明。代码审查器会检查哪些模块可以使用module->namespace以及非安全模块ffi/unsafe。
检查一个模块的protected导出需要一个比上述检查器更强的检查器。为了区分受信代码和非受信代码,可以先加载受信代码,然后执行(make-inspector (current-code-inspector))
来安装一个更弱的检查器。
像常量定义这样的语法对象,会保留其所在模块的审查器。
datum->syntax
有可能影响结果的语法对象。
编译后代码存在".zo"结尾的文件中是不受信的。
(本篇完)