【问题标题】:Scheme: difference between define and define-syntax-rule方案:define 和 define-syntax-rule 的区别
【发布时间】:2016-01-11 10:42:51
【问题描述】:

我在 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

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

-谢谢

【问题讨论】:

  • 尝试(if-fun #true 'something (/ 1 0)),然后尝试(if-mac #true 'something (/ 1 0)) ;)
  • 另外:在#lang racket 中尝试(define (if-lazy c thn els) (if c (thn) (els)))(if-lazy #t (lambda () 'something) (lambda () (/ 1 0)))。或者类似地尝试#lang lazy(print (if-fun #t 'something (/ 1 0)))

标签: macros scheme racket


【解决方案1】:

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

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

  • 使用函数和宏的方式完全相同:(thing other stuff)thing 是函数还是宏尚不清楚。这既好又坏。主要是非常好。

  • 至于定义事物,使用define-syntax-rule 定义宏的方式与define 函数的方式极为相似。这既好又坏。当你第一次学习时,我会说这很糟糕——因为它很容易忘记宏与函数有多么不同。这可能会令人困惑!


  • 当您调用函数时,在运行时所有的参数都会被评估,然后给函数。这就是为什么像(/ 1 0) 这样的if-fun 的参数会导致错误。在控制进入if-fun 之前对其进行评估(并引发除以零错误)。

    (旁注:当您使用延迟评估或手动“thunk”调用函数时,评估会延迟。if-lazy 能够调用thnels 过程参数,仅作为/当需要时。当条件为假时,它甚至不会尝试调用els。)

  • 当您调用宏时:

    1. 时间:宏在您的程序运行之前开始工作。宏在编译时起作用,而不是稍后在运行时起作用。

    2. 什么:宏将代码片段转换为其他代码片段。但是代码还没有被评估。代码仅在稍后的运行时评估。因此,if-mac 的“参数”不会被宏评估。他们只是插入到真正的if 表单的代码中,这是一个宏(或原始的特殊表单),只计算所需的内容。


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

【讨论】:

  • Greg,您的解释非常有见地,有助于区分两者。感谢您花时间向像我这样的新手详细说明。
【解决方案2】:
(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) ..... 到负无穷大。它永远不会返回一个值,但由于它不是尾递归的,它会耗尽内存。

(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的懒惰版本,if和其他特殊形式作为程序,因为需要评估。懒惰的语言不需要宏。

【讨论】:

    猜你喜欢
    • 2018-09-15
    • 2020-07-24
    • 2023-04-08
    • 2010-09-12
    • 2011-07-09
    • 1970-01-01
    • 2012-08-25
    • 2020-03-03
    • 2013-09-03
    相关资源
    最近更新 更多