Marvin's Blog【程式人生】

Ability will never catch up with the demand for it

08 Mar 2021

The Racket Guide阅读笔记【八】

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"结尾的文件中是不受信的。

(本篇完)