Scheme: define 和 define-syntax-rule 的区别

Scheme: difference between define and define-syntax-rule

我在 Racket 中得到了两个 if 语句指令:

(define (if-fun c thn els) (if c thn els))
(define-syntax-rule (if-mac c thn els) (if c thn els))

有人介意解释一下这两个 if 语句的计算方式之间的区别,并提供一个使用每个 if 语句定义的示例吗?在这个例子中,我很难区分宏和函数参数是如何求值的。我试过一些小例子,例如:

(if-fun (> 3 4) true false)  ;; #f
(if-mac (> 3 4) true false)  ;; #f

但显然这并不能帮助我区分这两个定义。

-谢谢

从您的评论来看,您似乎已经明白了这一点。关键问题是,什么时候(如果有的话)对事情进行评估?

另一个关键点是函数和宏是完全不同的,尽管它们的定义和使用看起来是一样的。

  • 使用函数和宏的方式完全相同:(thing other stuff)thing 是函数还是宏尚不清楚。这有好有坏。大部分都很好。

  • 至于定义的东西,使用define-syntax-rule定义宏的方式与define定义函数的方式极为相似.这有好有坏。当您第一次学习时,我会说这在很大程度上是非常糟糕的——因为它真的很容易忘记宏与函数有多么不同。可能会造成混淆!


  • 当您调用一个函数时,在运行时所有的参数被评估, then赋予函数。这就是为什么像 (/ 1 0) 这样的 if-fun 参数会导致错误。它在控制进入 if-fun.

    之前被评估(并引发除以零错误)

    (旁注:当你调用带有惰性求值的函数,或者使用手动 "thunks" 时,求值会延迟。if-lazy 可以调用 thnels 个过程参数,只需要 as/when 个。当条件为假时,它甚至不会尝试调用 els。)

  • 当您调用宏时:

    1. 时间: 宏在您的程序 甚至 运行s 之前完成它的工作。该宏在编译时起作用,而不是在 运行 之后。

    2. 内容: 宏将代码片段转换为其他代码片段。但是代码还没有被评估。代码仅在稍后评估,在 运行 时间。所以 "arguments" 到 if-mac 不会被宏计算。它们只是插入到真正的 if 形式的代码中,这是一个宏(或原始特殊形式),只计算所需的内容。


最后令人困惑的部分是,因为您的示例的 thenelse 表达式没有任何副作用,也不导致任何错误,差异不明显。

(define (verbose arg)
  (display arg) ; display 
  (newline)     ; display newline
  arg))         ; evaluate to arg

(if-fun (verbose (> 3 4)) 
        (verbose 'true) 
        (verbose 'false))
; ==>  false

这会打印

#f
true
false

宏版本:

(if-mac (verbose (> 3 4)) 
        (verbose 'true) 
        (verbose 'false))
; ==>  false

它打印

#f
false

你看出区别了吗?使用过程,每个参数都会被求值并绑定到变量,然后主体被求值。

在宏版本中,代码被转换,然后被评估。因此,结果表达式从未被执行,因为谓词是 #f.

如果您尝试创建递归函数:

(define (factorial n)
  (if-fun (<= n 2)
          n
          (* n (factorial (- n 1)))))

(factorial 2)

即使它达到基本情况,因为所有 3 个参数都被评估,所以它将执行 (factorial 1),然后 (factorial 0),(factorial -1) ..... 到负无穷大。它永远不会 return 一个值,但是,因为它不是尾递归的,它会 运行 内存不足。

(define (factorial n)
  (if-mac (<= n 2)
          n
          (* n (factorial (- n 1)))))

(factorial 2)

当程序被评估时,宏可以扩展成:

(define (factorial n)
  (if (<= n 2)
      n
      (* n (factorial (- n 1)))))

这就好像您根本没有使用您的宏一样。如果您有一个在扩展时打印某些内容的宏,那么在您使用它之前,它会在过程中每次使用时打印一次。

之所以这样,是因为Scheme和Racket有急切的评价。例如。 #!lazy racket 是#!racket 的惰性版本,if 和其他特殊形式由于评估是按需制作的。惰性语言不需要宏。