Scheme 中的 'and' 将忽略错误 '除以 0',例如 (and (negative? (random 100)) (/1 0)) 返回 #f。它是如何做到的?
i (define (∧ b₀ b₁) (if (not b₀) #f b₁)), (∧ (negative? (random 100)) (/ 1 0)) 仍然进入“除以 0”错误。
您不能and
直接定义为函数,因为 Scheme 是严格的——这意味着函数参数总是在传递给函数之前被评估。
and
但是,您可以使用宏定义适当的短路。这是 Racket 中最简单的版本:
(define-syntax-rule (my-and a b) (if a b #f))
syntax-rules
或标准方案中使用的等效形式:
(define-syntax my-and
(syntax-rules ()
[(_ a b) (if a b #f)]))
宏不是普通功能。相反,它是在编译时运行的句法转换。当您and
在代码中使用 new 时,它会“扩展”为相应的if
表达式。所以:
(my-and #f (/ 1 0))
变成
(if #f (/ 1 0) #f)
在你的程序运行之前。由于if
内置于语言中,因此具有正确的行为。
由于宏不是函数,这也意味着您不能作为参数传递。所以你不能and
直接使用写一个折叠——你必须把它包装成一个 lambda。
为了更忠实于原始and
,您可以my-and
通过使宏递归来定义采用任意数量的参数。要在宏中定义“剩余参数”,请使用特殊...
关键字:
(define-syntax my-and
(syntax-rules ()
[(_) #t]
[(_ a) a]
[(_ a b ...) (if a (my-and b ...) #f)]))
如果您使用的是诸如 Racket#lang lazy
或 Haskell 之类的惰性语言,则无需在此处使用宏。你可以直接定义and
:
#lang lazy
(define (and a b) (if a b #f))
或在 Haskell 中:
and a b = if a then b else False
作为正常功能,它将具有正确的行为。您可以将其传递and
给折叠,它甚至会在遇到False
!时立即停止评估列表 看一看:
Prelude> foldl and True [True, False, error "fail"]
False
(error
在 Haskell 中的错误就像1/0
. 由于 Haskell 是静态类型的,因此参数and
必须是布尔值,因此您不能1/0
直接使用。)
像大多数语言一样,Scheme 的逻辑AND
使用短路求值,这意味着它的右操作数只有在左操作数为真时才会被求值。如果左操作数为假,则无论右操作数的值如何,表达式的结果都必须为假,因此如果左操作数的计算结果为假,则立即返回假,根本不计算右操作数。
准确地说,这是规范中的语言(我是 R5RS 的第 4.2.1 节,但这不是一个可能在规范修订之间改变的领域):
(and <test
1>
... )
<test>
表达式从左到右计算,返回第一个计算结果为假值的表达式的值(参见第 6.3.1 节)。不会评估任何剩余的表达式。
布尔快捷方式。“and”的第一个参数计算为假,因此结果必须为假,并且没有理由计算第二个参数(因此会导致错误)。