Clojure 我可以不使用eval编写此宏吗?

Clojure 我可以不使用eval编写此宏吗?,clojure,macros,lisp,eval,Clojure,Macros,Lisp,Eval,我正在尝试编写一个宏,它将捕获Clojure中的编译时错误。具体地说,我希望捕获在调用尚未针对该数据类型实现的协议方法并抛出clojure.lang.Compiler$CompilerException时抛出的异常 到目前为止,我已经: (反宏捕获编译器错误 [正文] (试试看 (评估机构) (捕捉异常e))) 但当然,我被告知,eval是邪恶的,你通常不需要使用它。有没有一种方法可以在不使用eval的情况下实现此功能 我倾向于认为eval在这里是合适的,因为我特别希望在运行时而不是编译时对代码

我正在尝试编写一个宏,它将捕获Clojure中的编译时错误。具体地说,我希望捕获在调用尚未针对该数据类型实现的协议方法并抛出
clojure.lang.Compiler$CompilerException
时抛出的异常

到目前为止,我已经:

(反宏捕获编译器错误
[正文]
(试试看
(评估机构)
(捕捉异常e)))

但当然,我被告知,
eval
是邪恶的,你通常不需要使用它。有没有一种方法可以在不使用
eval
的情况下实现此功能


我倾向于认为
eval
在这里是合适的,因为我特别希望在运行时而不是编译时对代码进行评估。

宏在编译时展开。他们不需要
eval
code;相反,它们组装代码,稍后将在运行时进行评估。换句话说,如果要确保传递给宏的代码是在运行时而不是在编译时计算的,这就告诉您绝对不应该在宏定义中计算它

名称
catch编译器错误
有点用词不当;如果调用宏的代码有编译器错误(可能缺少括号),则宏实际上无法捕获它。您可以编写如下的
catch runtime error
宏:

(defmacro捕获运行时错误
[&正文]
`(试试看
~@体
(捕捉例外e#
e#))
以下是此宏的工作原理:

  • 接受任意数量的参数,并将它们存储在名为
    body
    的序列中
  • 创建包含以下元素的列表:
  • 符号
    try
  • 作为参数传入的所有表达式
  • 包含以下元素的另一个列表:
  • 符号
    catch
  • 符号
    java.lang.Exception
    Exception
    的限定版本)
  • 一个独特的新符号,我们可以在后面称之为
    e#
  • 我们之前创建的同一个符号
  • 这有点太难一下子咽下去了。让我们看看它对一些实际代码的作用:

    (宏扩展)
    “(捕获运行时错误
    (/ 4 2)
    (/ 1 0)))
    
    如您所见,我不是简单地以宏作为第一个元素来评估表单;这将扩展宏并计算结果。我只想执行扩展步骤,因此我使用了
    macroexpand
    ,这给了我以下信息:

    (试试看)
    (/ 4 2)
    (/ 1 0)
    (catch java.lang.Exception e__19785__自动__
    e_uuu19785_uuu自动_uuuu)
    
    这确实是我们所期望的:一个包含符号
    try
    、我们的身体表达式的列表,另一个包含符号
    catch
    java.lang.Exception
    的列表,后面是唯一符号的两个副本

    您可以通过直接计算宏来检查该宏是否执行了您希望它执行的操作:

    (捕获运行时错误(/42)(/10))
    ;=> #错误{
    ;原因“被零除”
    通过
    ;[{:键入java.lang.arithmetricException
    ;:消息“除以零”
    ;:在[clojure.lang.Numbers除以“Numbers.java”158]]
    痕迹
    ;[[clojure.lang.Numbers除以“Numbers.java”158]
    ;[clojure.lang.Numbers除以“Numbers.java”3808]
    ;     ,,,]}
    
    好极了。让我们用一些协议来试试:

    (def协议Foo)
    (福[这个])
    (除协议栏外)
    (除此之外)
    (数据记录Baz[]
    福
    (foo[uux]:qux)
    (捕获运行时错误(foo(->Baz)))
    ;=> :库克斯
    (捕捉运行时错误(条形图(->Baz)))
    ;=> #错误{,,}
    
    但是,如上所述,使用这样的宏无法捕获编译器错误。您可以编写一个宏,返回一段代码,对传入的其余代码调用
    eval
    ,从而将编译时间推回到运行时:

    (defmacro捕捉错误
    [&正文]
    `(试试看
    (eval'(do~@body))
    (捕捉例外e#
    e#))
    
    让我们测试宏扩展以确保其正常工作:

    (宏扩展)
    "(捕捉错误)
    (foo(->Baz))
    (foo(->Baz)无)
    
    这扩展到:

    (试试看)
    (clojure.core/eval)
    "(做)
    (foo(->Baz))
    (foo(->Baz)无)
    (catch java.lang.Exception e__20408__自动__
    e_uuu20408_uuu自动_uuuu)
    
    现在,我们可以捕获更多错误,例如由于尝试传递错误数量的参数而导致的
    IllegalArgumentException
    s:

    (捕捉错误(条(->Baz)))
    ;=> #错误{,,}
    (捕捉错误(foo(->Baz)nil))
    ;=> #错误{,,}
    
    但是(我想说得很清楚),不要这样做。如果你发现自己把编译时间推回到运行时只是为了捕捉这些错误,那么你几乎肯定是做错了什么。你最好重组你的项目,这样你就不必这么做了


    我猜你已经看到了,这很好地解释了
    eval
    的一些陷阱。特别是在Clojure中,除非您完全理解它在范围和上下文方面提出的问题,以及该问题中讨论的其他问题,否则您绝对不应该使用它。

    谢谢!这适用于我预期的应用程序,但是,我注意到,如果我将错误数量的参数传递给方法调用,它将无法捕获错误。相反,它抛出一个非法参数异常。知道为什么吗?@MONODA43您无法捕获该错误,因为它是在编译时抛出的。使用类似这样的
    try
    -
    catch
    可以在Clojure中捕捉到的唯一错误是运行时错误。这现在是有意义的。之所以要这样做,是因为我正在尝试创建一个工具,该工具通过编程方式确定哪些协议由给定的core.matrix实现实现。基本上是为了评估上述实施的完整性。你看到了吗?信息技术