【问题标题】:What is standard way of defining global closures in scheme?在方案中定义全局闭包的标准方法是什么?
【发布时间】:2019-09-15 20:09:58
【问题描述】:

所以我想知道是否有标准的方式来编写这样的代码:

(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 这样的其他方言是否允许这样的事情?

【问题讨论】:

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


    【解决方案1】:

    我认为围绕define 进行绑定的任何解决方案都不能以便携或安全的方式工作,因为被包装的define 要么会构造本地绑定(主体中的前导形式),要么是非法的(非前导正文中的表格),尽管我很乐意让一个符合 Scheme 标准的人指出我错在哪里。

    我认为像这种有点讨厌的黑客攻击应该可以工作。

    (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 所以我可以' t 轻松地在其中编写旧式 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
    > 
    

    【讨论】:

    • 不能根据“the”标准从 LET 内部定义全局变量,这难道不奇怪吗?这是一个不寻常的功能吗?
    • @RainerJoswig 好吧,我讨厌 CL (let (...) (defun ...) ...) 等价物,所以我有偏见。如果我们被允许使用 Racket,那么 (define-values (...) (let (...) ... (values ...))) 工作得很好,我认为 define-values 可能可以实现为宏。
    【解决方案2】:

    在最新的 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,所以虽然它不是很优雅,但它会做一些肮脏的工作。

    【讨论】:

    • 这很酷:我试图确定define-values 是否可以写成宏,结果证明可以。谢谢!
    • define-values可以用在let里面吗,还是和define、let、single lambda一样?如果它相同,那么它可以用于 let 位于生成函数的宏之外的情况。
    • @jcubic 它的工作方式与define 相同。如果你这样做(let () (define name ...)),那么name 会变成本地的,可能会变成letrec。因此,您 let 需要像我所展示的那样进入表达式。 define-values 在处理返回多个值的过程时也很有用,例如。 (define-values (q r) (quotient-and-remainder 15 7)) 这是本地绑定的用例。
    【解决方案3】:
    (let ((x 10))
      (somemacro x))
    

    ->

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

    在 Common 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 不在 顶级。如果在顶级的文件中定义了一个函数,那么在编译时文件编译器会看到这是一个函数,并且可能会做一些特殊的事情,比如在编译时环境中记录签名,内联它。等等

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

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

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

    【讨论】:

    • clhs.lisp.se/Body/s_eval_w.htm 中的最后一段说,忽略 eval-when 可能对 defun/defmacro 有用,而描述说编译时副作用仅在 eval-when 是顶层表格。也许有一种方法可以通过将整个内容包装在 eval-when 中来实现 OP 想要的(我同意这是不好的风格,顺便说一句)
    • 将扩展功能有(+ x 1)(+ 10 1) 我需要的是(+ x 1)。我正在尝试简化我遇到的实际问题,我有需要关闭的对象而不是评估并放入 lisp 代码中,因为它不能像数字 10 那样具有嘴唇表示。
    • @jcubic:扩展函数有x
    • 最后一个示例在某些方案中有效,而在其他方案中无效,我认为它在以前的诡计或 Kawa 中有效(Java 中的方案)。
    • 你能解释一下那个空列表在方案中的作用吗?这似乎在 biwascheme 中有效,但不知道为什么。如果不起作用,则没有空列表。
    猜你喜欢
    • 2012-04-11
    • 1970-01-01
    • 2020-05-23
    • 2017-04-26
    • 1970-01-01
    • 2016-09-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多