【问题标题】:While Loop Macro in DrRacketDrRacket 中的 While 循环宏
【发布时间】:2012-06-13 15:44:49
【问题描述】:

我正在尝试在 DrRacket 中为 while 循环创建一个宏。这是我写的:

(require mzlib/defmacro)

(define-macro my-while
  (lambda (condition  body)
    (list 'local (list (list 'define (list 'while-loop)
                             (list 'if condition
                                   (list body (list 'while-loop))
                                   '(void))))
          '(while-loop))))


(define x 0)

(my-while (< x 10)
          (begin              
            (display x)
            (newline)
            (set! x (+ x 1))))

这个程序的输出是:

0
1
2
3
4
5
6
7
8
9
error:  procedure application: expected procedure, given: #<void>; arguments were: #<void>

有人可以帮我解决这个问题吗?为什么这个宏不直接终止并返回 void。似乎当 condition 不为真时,系统会尝试将 void 作为参数应用到某个过程。

【问题讨论】:

    标签: macros scheme racket


    【解决方案1】:

    哎哟:

    1. 使用这种风格的while 循环会鼓励过度使用命令式编程。
    2. 使用define-macro 会创建不卫生的宏,这在Scheme 中是一场噩梦。

    虽然我不鼓励编写命令式循环宏,但供您参考,这里有一个相同宏的非define-macro 版本:

    (define-syntax-rule (my-while condition body ...)
      (let loop ()
        (when condition
          body ...
          (loop))))
    

    它使用syntax-rules,它创建了卫生的宏,并且比你所拥有的更容易阅读。


    现在,对于您的问题的实际答案,首先,让我们以更易读的方式编写您的原始宏:

    (define-macro my-while
      (lambda (condition body)
        `(local ((define (while-loop)
                   (if ,condition
                       (,body (while-loop))
                       (void))))
           (while-loop))))
    

    一旦你这样写出来,你就会看到真正的问题出在哪里:在(,body (while-loop)) 行中,而应该是(begin ,body (while-loop))

    【讨论】:

    • 真实而真实。但我今天第一次看到宏(在 Fixnum Days 的自学计划中)。还在学习。我还没有阅读有关卫生宏的内容。
    • @RajeshBhat:另外,我刚刚更新了我的答案,解释了为什么你的不卫生宏不起作用。 :-) 当然,我仍然鼓励您使用卫生版。 ;-)
    • @RajeshBhat 宏是一项有价值的研究。 Scheme in a Fixnum Days 很好,但是关于宏的部分已经过时了。如果我是你,我会找到关于使用语法规则或语法大小写的宏的介绍。我可以推荐球拍指南中的介绍:docs.racket-lang.org/guide/pattern-macros.html
    【解决方案2】:

    while 的另一个版本使用 do 循环:

    (define-syntax while
      (syntax-rules ()
        ((while pred? stmt ...)
          (do () ((not pred?))
            stmt ...))))
    

    【讨论】:

    • 这确实有效,尽管do 循环通常会宏扩展为基于命名的-let 循环,而且许多计划者告诉我命名为let 循环更惯用比do 循环更... CL 风格。 ;-)
    【解决方案3】:

    当一个普通的旧函数可以使用时,为什么要使用宏?

    ;; fun-while : (-> Boolean) (-> Any) -> Void
    (define (fun-while condition body)
      (when (condition)
        (body)
        (fun-while condition body))
    

    当然,这需要你传入可以调用的可重复操作(这就是为什么conditionbodyfun-while 的主体中用括号括起来),所以如果你需要一个宏想要更漂亮的语法。但是一旦你有一个具有所需行为的函数,在这种情况下添加一些糖是微不足道的:

    (define-syntax-rule (my-while condition body ...)
       (fun-while (lambda () condition)
                  (lambda () body ...)))
    

    现在,正如已经说过的,这鼓励了命令式风格,这是不受欢迎的。不要突变,而是尝试使状态显式:

    ;; pure-while : forall State.
    ;;    (State -> Boolean)   ; the "condition" that inspects the state
    ;;    (State -> State)     ; the "body" that moves from one state to the next
    ;;    ->   ; curried
    ;;    State                ; the current state
    ;; -> State                ; produces the ending state
    (define ((pure-while condition make-next) current-state)
       (if (condition current-state)
           (pure-while condition make-next (make-next current-state))
           current-state))
    

    您会注意到,前两个参数现在是来自 State 的函数,并且应用到 2 个参数的结果也是来自 State -&gt; State 的函数。这是一个反复出现的模式,作为 Haskeller,我称之为“状态单子”。不过,将糖放在这个概念之上的讨论有点超出了本次讨论的范围,所以我就停在那里。

    【讨论】:

      【解决方案4】:

      因为已经有一段时间了:

      Racket 6.0 的 while

      #lang racket
      
      (define-syntax while
        (syntax-rules ()
          ((_ pred? stmt ...)
           (do () ((not pred?))
            stmt ...))))
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-11-02
        • 2018-01-16
        • 1970-01-01
        • 2012-02-16
        • 1970-01-01
        相关资源
        最近更新 更多