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

时间:2019-04-26 10:16:13

标签: macros scheme lisp common-lisp lisp-macros

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

(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宏。

如果方案不提供类似支持,那么其他方言(例如Common Lisp)是否允许此类支持?

3 个答案:

答案 0 :(得分:3)

(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 < / em>。如果在顶层的文件中定义了函数,则在编译时,文件编译器会看到这是一个函数,并且可能会做一些特殊的事情,例如在编译时环境中注意签名,内联它。等

请注意,当Scheme中的DEFINE定义了一个本地函数时,一个可能仍然可以执行(取决于实现对标准的附加作用):

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

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

答案 1 :(得分:3)

我认为,围绕define包裹绑定的解决方案根本无法移植或安全地工作,因为包裹的define会构造局部绑定(主体中的前导形式)或是非法的(体内的非领导形式),尽管我很乐意让Scheme-standards人士指出我错了。

相反,我认为类似这种有点讨厌的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的常量,它表示“尚未正确定义”:Racket在racket/undefined中提供了此常量,但通常可以是任何东西:您可以在某处说

(define undefined 'undefined)

例如。

诀窍是使用占位符值在顶层定义所需的内容,然后在let内将其分配给它们。

我确定可以定义一个宏,并将其扩展为以下内容(这就是为什么整个内容都放在begin中的原因):我没有这么做,因为它很简单,我使用了Racket,所以我无法轻松地在其中编写旧式Lisp宏。


请注意,现代方案中最明显的方法是使用define-values

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

做您想要的。如另一个答案所述,如果您只有多个值,则可以将define-values作为宏来实现。但是,如果根本没有多个值,则可以使用根据结果的列表定义事物的事物:

(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
> 

答案 2 :(得分:3)

在最新的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,因此虽然它不是很优雅,但是却很脏。