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
可以调用 thn
或els
个过程参数,只需要 as/when 个。当条件为假时,它甚至不会尝试调用 els
。)
当您调用宏时:
时间: 宏在您的程序 甚至 运行s 之前完成它的工作。该宏在编译时起作用,而不是在 运行 之后。
内容: 宏将代码片段转换为其他代码片段。但是代码还没有被评估。代码仅在稍后评估,在 运行 时间。所以 "arguments" 到 if-mac
不会被宏计算。它们只是插入到真正的 if
形式的代码中,这是一个宏(或原始特殊形式),只计算所需的内容。
最后令人困惑的部分是,因为您的示例的 then 和 else 表达式没有任何副作用,也不导致任何错误,差异不明显。
(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
和其他特殊形式由于评估是按需制作的。惰性语言不需要宏。
我在 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
可以调用thn
或els
个过程参数,只需要 as/when 个。当条件为假时,它甚至不会尝试调用els
。)当您调用宏时:
时间: 宏在您的程序 甚至 运行s 之前完成它的工作。该宏在编译时起作用,而不是在 运行 之后。
内容: 宏将代码片段转换为其他代码片段。但是代码还没有被评估。代码仅在稍后评估,在 运行 时间。所以 "arguments" 到
if-mac
不会被宏计算。它们只是插入到真正的if
形式的代码中,这是一个宏(或原始特殊形式),只计算所需的内容。
最后令人困惑的部分是,因为您的示例的 then 和 else 表达式没有任何副作用,也不导致任何错误,差异不明显。
(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
和其他特殊形式由于评估是按需制作的。惰性语言不需要宏。