【问题标题】:Racket - implementing the let* function using macroRacket - 使用宏实现 let* 函数
【发布时间】:2017-10-14 14:53:44
【问题描述】:

我需要使用 defmacro 来实现 my_let*,它的工作原理与 let* 类似,但是当 let* 扩展为一系列嵌套的 let 调用(在幕后)时,my_let* 需要扩展到 single let 调用,并使用 define 语句来定义我得到的参数。

使用 my_let* 的示例:

 (my_let* ((a 2)
 (b 3)
 (c (+ a b)))
 (+ a b c))

并且这段代码的返回值应该是 10。就像使用 let* 一样。 上面的代码将在 my_let* 中扩展为以下内容:

(let ()
 (define a 2)
 (define b 3)
 (define c (+ a b))
 (+ a b c))

我是使用宏的新手,虽然我成功编写了一些宏,但这个让我迷失了。
提前谢谢你。

【问题讨论】:

  • 两个问题:你真的想使用defmacro,Racket 出于兼容性原因确实有,而不是(更容易)define-syntaxsyntax-rules 和类似的?此外,您建议的扩展与let* 并不完全相同,因为let* 允许您隐藏以前的绑定,而define 不允许。你同意吗?
  • @BrendanCannell 在这种情况下是的,因为它是分配的一部分(其中一部分是宏)。我仍然想了解定义语法的使用。 (也许是将来要学习的课程的一部分)。
  • 好的。也许您可以告诉我们您已经尝试过什么以及您在想什么,而不是有人只是给您答案,这样我们就可以为您指明正确的方向。
  • @BrendanCannell 如上所述。这个让我迷路了。我确实成功地编写了其他宏,但即使使用 (define x y) 来定义变量对我来说也是新的,因为我们总是使用 (let x y) 来实现此目的。另外,这里我也说不清内部辅助函数应该做什么。
  • 我无法合理猜测您的定义出了什么问题,因为您没有将其提供给我们。那么唯一的答案就是简单地为您编写代码。甚至没有什么可以解释的。这真的是你想要的吗?

标签: macros scheme lisp racket let


【解决方案1】:

使用syntax-parse。至少不要考虑在 Racket 中使用defmacro

#lang racket

(require (for-syntax syntax/parse))

(define-syntax (my-let* stx)
  (syntax-parse stx
    [(_my-let* ([name:id e:expr] ...) body ...)
     #'(let ()
         (define name e) ...
         body ...)]))

name:id 表示 name 必须是标识符,e:expr 表示 e 必须是一个表达式。这些简单的注释有助于语法解析 为您提供更好的错误消息。

例子:

(my-let* ((4 2)
          (b 3)
          (c (+ a b)))
         (+ a b c))

在这里,DrRacket 将为 4 阅读内容着色并给出消息:

my-let*: expected identifier in: 4

【讨论】:

    【解决方案2】:

    Scheme方式是使用syntax-rules

    (define-syntax my-let*
      (syntax-rules ()
        ((_ ((binding expression) ...) body ...)
         (let ()
           (define binding expression) ...
           body ...))))
    

    使用defmacro更像是做一个程序。

    (define (my-let-fun* bindings . body)
      ...)
    

    它的工作原理是这样的:

    (my-let-fun* '((a 1) (b 2) (c (+ a b))) "test" '(list a b c))
    ; ==> (let () (define a 1) (define b 2) (define c (+ a b)) "test" (list a b c))
    

    如果您在实现中没有调用 my-let-fun*,只需将其更改为 defmacro 即可。

    (defmacro my-let* (bindings . body)
      ...)
    

    使用帮助器进行递归或使用foldr 进行绑定都非常简单。祝你好运!

    您的my-let* 只能在#lang racket 以及#!r6rs 及更高版本中使用。在这种情况下,在 R5RS 中你会得到一个错误:

    (my-let* ((a 1) (b 2) (c (+ a b)))
      (list a b c))
    ; signals an error that a is undefined.
    

    原因是它扩展成这样:

    (let ((a 'undefined) (b 'undefined) (c 'undefined))
      (let ((tmp1 1) (tmp2 2) (tmp3 (+ a b)))
        (set! a tmp1)
        (set! b tmp2)
        (set! c tmp3))
      (list a b c))
    

    【讨论】:

      【解决方案3】:

      在错误消息和对宏步进器的一些明智使用之间,我认为在这里很难出错。麻烦在于确保您使用 conses 或 unquote-splicing 将所有内容正确组合在一起。我相信此类宏的标准做法是大量使用 quasiquote 和 unquote-splicing,以使输出尽可能地匹配预期的语句,否则宏可能变得非常难以理解。但我不是defmacro 专家。

      #lang racket/base
      (require (for-syntax racket/base)
               compatibility/defmacro)
      
      (defmacro my-let* (binding-pairs . body)
        (define defines (map (lambda (bp) (cons 'define bp)) binding-pairs))
        `(let ()
           ,@defines
           ,@body))
      
      (my-let* ((a 2) 
                (b (expt a 3)))
        (printf "a:~a\nb:~a\n" a b)
        (+ a b))
      

      【讨论】:

        猜你喜欢
        • 2011-02-22
        • 2021-06-02
        • 1970-01-01
        • 2021-12-09
        • 2011-10-13
        • 2015-12-13
        • 1970-01-01
        • 2020-06-02
        • 2014-06-21
        相关资源
        最近更新 更多