这是一个 XY 问题。宏是语言语法的一部分:它们不是函数,您不能将它们应用于参数。从概念上讲,宏就像将源代码映射到其他源代码的函数,并且在编译时而不是运行时调用:尝试在运行时使用它们是一种类别错误。 (在 Common Lisp 中,宏就是将源代码映射到其他源代码的函数:在 Scheme 中我不太清楚)。
因此,如果您有一个列表并且您想知道它的所有元素是否为真,您可以调用列表上的一个函数来执行此操作。
这样的函数很容易写:
(define (all-true? things)
(cond [(null? things)
#t]
[(first things)
(all-true? (rest things))]
[else #f]))
然而,Racket 提供了一个更通用的功能:andmap:如果things 中的一个不为真,(andmap identity things) 要么返回 false,要么返回列表中最后一个事物的值(或 #t如果列表为空)。例如,(andmap (lambda (x) (and (integer? x) (even? x))) ...) 会告诉您列表中的所有元素是否都是偶数。
还有来自SRFI 1 的every,您可以在(require srfi/1) 之后在Racket 中使用它。它大部分(完全?)与andmap 相同。
人们有时会尝试做的一件事(您似乎很想这样做)是使用eval。可能还不清楚eval 的解决方案有多糟糕;是。这很糟糕,因为
- 事实上,它根本不起作用;
- 就其工作而言,它会阻止任何类型的编译和优化;
- 最后但同样重要的是,它是代码注入攻击的途径。
让我们看看它有多糟糕。从这个开始:
> (let ([args '(#t #t #f)])
(eval `(and ,@args)))
#f
好的,看起来不错,对吧?好吧,如果我有一个 (a a a b) 列表怎么办:其中没有一个元素是错误的,所以,让我们试试吧:
> (let ([args '(a a b)])
(eval `(and ,@args)))
; a: undefined;
; cannot reference an identifier before its definition
哦,好吧,我可以解决这个问题吗?
> (let ([args '(a a b)]
[a 1] [b 2])
(eval `(and ,@args)))
; a: undefined;
; cannot reference an identifier before its definition
不,我不能。为了使这项工作,我需要这个:
> (define a 1)
> (define b 2)
> (let ([args '(a a b)])
(eval `(and ,@args)))
2
或者这个
> (let ([args '('a 'a 'b)])
(eval `(and ,@args)))
'b
是的,这些是引用列表中的引号。
这太可怕了:只有两种情况它会起作用,所有东西要么在顶层定义为 eval 无法访问它被调用的词法范围,要么对象中的文字可能已经是此处的文字,因为现在所有内容都被评估了两次。
所以这太可怕了。更糟糕的是,eval 评估 方案源代码。所以忘记编译、性能或任何好东西吧:一切都过去了(如果你有一个 JIT 编译器,它可能不会那么糟糕)。
哦,是的,eval 评估 Scheme 源代码,它评估所有代码。
所以我有这个方便的列表:
(define args
'((begin (delete-all-my-files)
(publish-all-my-passwords-on-the-internet)
(give-all-my-money-to-tfb)
#t)
(launch-all-the-nuclear-missiles)))
这只是符号列表和#t 的列表,对吗?所以
> (eval `(and @,args)
; Error: you are not authorized to launch all the missiles
; (but all your files are gone, your passwords are now public,
; and tfb thanks you for your kind donation of all your money)
哎呀。
很高兴能够说,如果我有一份清单,我想检查其中的一些财产,这样做不会把我所有的钱都寄给互联网上的某个人。事实上,很高兴知道检查列表的属性会完全停止。但是如果我使用eval,我无法知道:检查列表中的每个元素是否(评估为)true 可能永远不会终止,或者可能会发射核武器,我通常永远无法提前知道是否它将终止,或者它是否会发射核武器。这是一个……不受欢迎的属性。
至少我需要做这样的事情来严格限制可以出现在列表中的内容:
(define (safely-and-list l)
(for ([e (in-list l)])
(unless
(or (number? e)
(boolean? e))
(error 'safely-and-list "bad list")))
(eval `(and ,@l)))
但是……等等:我刚刚检查了列表中的每个元素:我为什么不检查一下它们都是真的然后 ?
这就是为什么eval 永远不是这个问题的正确解决方案。 eval 是正确的解决方案是评估 Scheme。如果您想编写一些程序来读取用户输入并进行评估,那么eval 非常适合:
(define (repl (exit 'exit))
(display "feed me> ")
(flush-output)
(let ([r (read)])
(unless (eqv? r exit)
(writeln (eval r))
(repl exit))))
但是,如果您想将宏应用于某些参数,那么您几乎肯定会遇到 XY 问题:您想做其他事情,而您可能不理解宏。