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

(本篇完)