在方案中定义全局闭包的标准方法是什么?

What is standard way of defining global closures in scheme?

所以我想知道是否有这样的代码的标准方法:

(let ((x 10))
  (define (add10 a)
     (+ x a)))

我知道:

(define add10 (let ((x 10))
                 (lambda (a) (+ x a))))

但是如果我想定义多个函数,这将不起作用,我需要知道标准方法,这样我才能编写定义函数的宏。你可以在里面调用宏 let:

(let ((x 10))
  (macro x))

例如,宏将创建函数列表:

(let ((x 10))
  (define (add1)
     (+ x 1))
  (define (add2)
     (+ x 2))
  (define (add3)
     (+ x 3)))

是否有定义函数 add1..add3 的标准方法?在我测试的方案中,这些函数将在 let 内部是本地的,并且在外部无法访问。

如果您显示宏代码,我只对带有 define-macroquasiquote 的 lisp 宏感兴趣,请不要 define-syntax,因为这主要用于我自己的 lisp (基于方案)我只有 lisp 宏。

如果 scheme 不支持这样的东西,其他方言如 Common Lisp 是否允许这样的东西?

(let ((x 10))
  (somemacro x))

->

(let ((x 10))
  (define (add1)
     (+ x 1))
  (define (add2)
     (+ x 2))
  (define (add3)
     (+ x 3)))

在普通 Lisp 中:

CL-USER 43 > (defmacro somemacro (var)
               `(progn
                  (defun add1 () (+ ,var 1))
                  (defun add2 () (+ ,var 2))
                  (defun add3 () (+ ,var 3))))
SOMEMACRO

CL-USER 44 > (let ((x 10))
               (somemacro x))
ADD3

CL-USER 45 > (add1)
11

CL-USER 46 > (add3)
13

人们有时会看到。一般来说,这在 Common Lisp 中是有点糟糕的风格,因为文件编译器将不会识别出全局函数声明,因为在 LET 内部 DEFUN 不在 top-等级。如果一个函数在文件中定义在 顶层 ,那么在编译时文件编译器会看到这是一个函数,并且可能会做一些特殊的事情,比如在编译时记录签名 -时间环境,内联它。等等

请注意,当 Scheme 中的 DEFINE 定义了一个局部函数时,仍然 可能 能够做到(取决于实现对标准的补充):

(let ((x 10))
  ()
  (define (add1) (+ x 1)))

请注意,在 Common Lisp 中,defun 定义全局函数,flet / labels 定义局部函数。

我认为围绕 define 包装绑定的解决方案根本无法移植或安全地工作,因为包装的 define 将构建本地绑定(正文中的前导形式)或是非法的(正文中的非前导形式),尽管我很乐意让符合 Scheme 标准的人指出我错在哪里。

相反,我认为像这种有点讨厌的 hack 应该有用。

(begin
  (define inc undefined)
  (define dec undefined)
  (let ((x 3))
    (set! inc (lambda (y)
                (set! x (+ x y))
                x))
    (set! dec (lambda (y)
                (set! x (- x y))
                x))))

这里我依赖于一个名为 undefined 的常量,这意味着 'not yet defined properly':Racket 在 racket/undefined 中提供了这个,但它通常可以是任何东西:你可以在某个地方,说

(define undefined 'undefined)

例如。

诀窍是用占位符值在顶层定义你想要的东西,然后在 let.

中分配给它们

我确定可以定义一个宏,它可以扩展为类似的东西(这就是为什么我将整个东西都放在 begin 中):我没有这样做,因为它很笨拙,而且我使用 Racket所以我不能轻易地在里面写老式的 Lisp 宏。


请注意,现代方案中显而易见的方法是使用 define-values:

(define-values (x y) (let (...) (values ...)))

做你想做的事。如另一个答案中所述,如果您只有多个值,则可以将 define-values 实现为宏。但是,如果您根本没有多个值,那么您可以使用基于 list 结果定义事物的东西:

(define-list (x y) (let (...) (list ...)))

这是该宏的两个粗略变体:第一个使用 Racket 的本机宏:

(require racket/undefined)

(define-syntax define-list
  (syntax-rules ()
    [(define-list () expr)
     (let ((results expr))
       (unless (zero? (length results))
         (error "need an empty list"))
       (void))]
    [(define-list (name ...) expr)
     (begin
       (define name undefined)
       ...
       (let ([results expr])
         (unless (= (length results)
                    (length '(name ...)))
           (error "wrong number of values"))
         (set! name (begin0
                      (car results)
                      (set! results (cdr results))))
         ...))]))

而第二个在 Racket 中使用不卫生的宏:

(require compatibility/defmacro
         racket/undefined)

(define-macro (define-list names expr)
  `(begin
     ,@(let loop ([ntail names] [defs '()])
         (if (null? ntail)
             (reverse defs)
             (loop (cdr ntail)
                   (cons `(define ,(car ntail) undefined) defs))))
     (let ([results ,expr])
       (unless (= (length results)
                  (length ',names))
         (error "wrong number of values"))
       ,@(let loop ([ntail names] [i 0] [assignments '()])
           (if (null? ntail)
               (reverse assignments)
               (loop (cdr ntail) (+ i 1)
                     (cons `(set! ,(car ntail) (list-ref results ,i))
                           assignments)))))))

请注意,这两种方法都没有经过测试,我需要花点时间说服自己第二种方法足够卫生。

但是有了这些:

> (define-list (inc dec)
    (let ([i 0])
      (list
       (lambda ()
         (set! i (+ i 1))
         i)
       (lambda ()
         (set! i (- i 1))
         i))))
> inc
#<procedure>
> (inc)
1
> (dec)
0
> (dec)
-1
> 

在最新的 Scheme 报告 R7RS 中,我们有 define-values。可以这样使用:

(define-values (add1 add2 add3)
  (let ((x 10))
    (values (lambda () (+ x 1))
            (lambda () (+ x 2))
            (lambda () (+ x 3)))))

当然,对于较大的块,可能需要进行局部定义并在 values 中引用它。

在 R7RS 报告中,您会找到适用于 R6RS 和 R5RS 的 define-values 语法规则。它使用 call-with-values 将值传递给 list,然后从那里传递 define。我敢打赌它也在 lambdas sucn 内部工作,Scheme 实现实际上可以将其转换为 letrec 所以虽然它不是很优雅但它完成了肮脏的工作。