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

单片43

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

到目前为止,我有:

(defmacro catch-compiler-error [body] (try (eval body) (catch Exception e e)))

但是,当然,有人告诉我这eval是邪恶的,您通常不需要使用它。有没有一种无需使用即可实现的方法eval

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

山姆·埃斯特普(Sam Estep)

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

考虑到这个名称,这个名称catch-compiler-error有点用词不当;如果调用您的宏的代码出现编译器错误(也许缺少括号),则您的宏实际上并没有采取任何措施来捕获它。您可以编写这样的catch-runtime-error宏:

(defmacro catch-runtime-error
  [& body]
  `(try
     ~@body
     (catch Exception e#
       e#)))

此宏的工作方式如下:

  1. 接受任意数量的参数并将其存储在称为的序列中body
  2. 创建包含以下元素的列表:
    1. 符号 try
    2. 所有作为参数传入的表达式
    3. 带有这些元素的另一个列表:
      1. 符号 catch
      2. 符号java.lang.Exception(的限定版本Exception
      3. 一个独特的新符号,我们以后可以将其称为 e#
      4. 我们之前创建的相同符号

可以一次吞掉所有内容。让我们看一下它对一些实际代码的作用:

(macroexpand
 '(catch-runtime-error
    (/ 4 2)
    (/ 1 0)))

如您所见,我并不是简单地将宏作为第一个元素来评估表单。这将扩展宏评估结果。我只想执行扩展步骤,所以我正在使用macroexpand,这给了我这个:

(try
  (/ 4 2)
  (/ 1 0)
  (catch java.lang.Exception e__19785__auto__
    e__19785__auto__))

这确实是我们所期望的:包含符号列表try,我们的身体表达,并用符号另一个列表catchjava.lang.Exception后跟的独特象征的两个副本。

您可以通过直接评估该宏来检查该宏是否可以实现您想要的功能:

(catch-runtime-error (/ 4 2) (/ 1 0))
;=> #error {
;    :cause "Divide by zero"
;    :via
;    [{:type java.lang.ArithmeticException
;      :message "Divide by zero"
;      :at [clojure.lang.Numbers divide "Numbers.java" 158]}]
;    :trace
;    [[clojure.lang.Numbers divide "Numbers.java" 158]
;     [clojure.lang.Numbers divide "Numbers.java" 3808]
;     ,,,]}

优秀。让我们尝试一些协议:

(defprotocol Foo
  (foo [this]))

(defprotocol Bar
  (bar [this]))

(defrecord Baz []
  Foo
  (foo [_] :qux))

(catch-runtime-error (foo (->Baz)))
;=> :qux

(catch-runtime-error (bar (->Baz)))
;=> #error {,,,}

但是,如上所述,使用这样的宏根本无法捕获编译器错误。可以编写一个宏,返回一个代码块,该代码块将调用eval传入的其余代码,从而将编译时间推回到运行时:

(defmacro catch-error
  [& body]
  `(try
     (eval '(do ~@body))
     (catch Exception e#
       e#)))

让我们测试宏扩展以确保其正常工作:

(macroexpand
 '(catch-error
    (foo (->Baz))
    (foo (->Baz) nil)))

扩展为:

(try
  (clojure.core/eval
   '(do
      (foo (->Baz))
      (foo (->Baz) nil)))
  (catch java.lang.Exception e__20408__auto__
    e__20408__auto__))

现在我们可以捕获更多错误,例如IllegalArgumentExceptions ,这些错误是由于尝试传递错误数量的参数而引起的:

(catch-error (bar (->Baz)))
;=> #error {,,,}

(catch-error (foo (->Baz) nil))
;=> #error {,,,}

但是(我想说得很清楚),不要这样做。如果您发现自己将编译时间拖回运行时只是为了捕捉这些错误,那几乎肯定是在做错什么。您最好重组项目,这样就不必这样做了。

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

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

您可以不使用Deferred来编写此代码吗?

我可以不使用.each()来呈现此集合吗?

如何在不使用`eval`的情况下编写此宏?

我可以不使用响应式解决此问题吗?

我可以使用 SUMPRODUCT 来完成此操作吗?

您可以使用变量名来引用对象而不使用eval()吗?

我可以使此宏更高效或更快速吗?

我可以在不使用两个循环的情况下执行此任务吗?

我可以不使用if语句中的break来做到这一点吗?

我可以不使用fork来执行Shell或系统调用吗?

我可以不使用FileReader文件选择选项来读取文本文件吗

我可以不使用计数变量来解决asyncMap吗?

我可以使用已经编写的Jmh微基准来预热生产代码吗

我可以在angularjs的$ scope函数内编写此函数吗

我该如何编写此C函数,以便用户可以在以后不使用“免费”的情况下调用它?

它是什么?C ++宏功能?如何替换此宏?我可以举个例子吗?

可以不使用save()来创建createRecord()吗?

可以使用Reduce()编写此函数吗?

我可以在不使用Python 3.6中的元类的情况下编写abc.ABC吗?

为什么我可以等待此代码但不使用.then?

我可以使用“重命名”实用工具来简化此使用“查找”和“ Mv”的脚本吗?

可以编写一条jQuery行来执行此隐藏和淡入吗?

我可以编写“内嵌”编辑器来编写提交消息吗?

我可以使用什么模式来编写 CRUD 功能?

我可以使用一种更优雅/更优化的方法来制作此连接算法吗?

我可以将 `eval` 与 `rolling()` 一起使用吗?

如何在不使用Lambda的情况下编写此代码,并且仍然可以正常工作?

是否可以在不使用do表示法的情况下编写此代码?

我可以在这里不使用线程同步吗?