【问题标题】:How to `apply` a macro/syntax in Racket?如何在 Racket 中“应用”宏/语法?
【发布时间】:2021-05-10 08:45:57
【问题描述】:

假设我有一个参数列表args 和一个宏/语法f,它接受可变数量的参数。如何将f 申请到args?显然apply 在这里不起作用。

例如,假设我有一个值列表bs,我想知道它们是否全部为真,所以我尝试(apply and bs),但我收到错误"and: bad syntax"。我想出的解决方法是(eval `(and . ,bs)),但我想知道是否有一些标准的方法来实现这种事情。

更新

已经建议了一堆可能的重复项,但其中大多数只是and 示例。这个建议的question 似乎和我的一样,但那里的答案不是很有帮助:它基本上说“不要这样做!”。

所以也许关键是在实践中这个“apply + 宏”问题只针对像andor 这样的宏,并且没有有用的一般问题?我在and 肯定遇到过这个问题,并且没有太多的Scheme 经验,当然没有其他这种现象的例子。

【问题讨论】:

标签: scheme racket


【解决方案1】:

这是一个 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 1every,您可以在(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 问题:您想做其他事情,而您可能不理解宏。

【讨论】:

  • 问题是“如果我想要使用这样的宏怎么办?” eval 技巧似乎有效,但也许有问题?但是基于所有关于and 的建议重复问题,我认为这个问题可能只出现在and 类宏中,在这种情况下,解决方案是使用这些宏的函数版本,比如@ 987654356@你建议。我对 Scheme 很陌生,并且(可能错误地)假设我遇到了某种一般性问题。
  • 如果你想使用这样的宏,你对宏是什么有一个根本的困惑。不,它不仅适用于像 and..这样的宏。
猜你喜欢
  • 1970-01-01
  • 2014-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-22
  • 1970-01-01
  • 2012-10-24
  • 1970-01-01
相关资源
最近更新 更多