Clojure 宏:仅在定义了符号时执行操作



我有一个宏defprinter,我可以在其中定义以下函数:根据解构+打印该变量在dic中获取值。

这看起来像:

(defmacro defprinter [name pattern] 
(list 'defn name [pattern] '(prn to-print)))

所以我可以做一些类似(defprinter print-lemons {to-print :lemons})然后(print-lemons {:lemons "lemons"})的事情,它会打印正确的东西(我可以在第一个参数上使用任何类型的解构来定义打印机)。

但是现在我想给出该功能的选项,也许也知道如何使用颜色打印,例如,如果定义了符号color,它应该(prn color "-colored " to-print),否则只是(prn color)

因此,(defprinter print-lemons {to-print :lemons})生成的函数应该与上面相同,而(defprinter print-lemons {to-print :lemons, color :color})将编写一个执行彩色版本的函数。

此外,如果我这样做,我想要同样的效果(let [color "black"] (defprinter print-lemons {to-print :lemons}))(def color "black") (defprinter print-lemons {to-print :lemons}).

这可能吗?

我尝试过按以下方式编写它:

(defmacro defprinter [name pattern] 
(list 'defn name [pattern]
'(try (eval '(prn (str color "-colored " to-print))) 
(catch java.lang.RuntimeException _ 
(prn to-print)))))

根据我的理解,宏将编写的函数将尝试在运行时评估表达式,如果未定义color则失败并显示 RuntimeException,然后执行 (prn to-print)。即使它会在运行时检查color是否存在,to-print(该函数始终需要存在)也会在宏扩展时在编译时检查。

但是这里发生的事情是,即使定义了color,我总是得到一个 RuntimeException(即使我只在 eval 语句上留下to-print,它也找不到它,但 catch 中的子句工作正常)。似乎该符号没有像我在 eval 期间所期望的那样得到解决,但我想不出任何其他方法来实现这一目标。

有什么建议吗?

首先,将您在这里处理的两个问题分开可能是有意义的:定义函数,以及根据可用的变量/局部变量确定如何打印。使宏尽可能简单往往会更容易弄清楚发生了什么。

在决定打印什么时,您实际上有 3 种情况:

  1. color是本地人
  2. color是一个变量(或类,我猜?
  3. color未定义

&env是一个有用且很少使用的工具(仅在宏中可用),可让您查看本地可用的内容。

resolve让你看看这个名字有哪些变量。

因此,在此示例中,有 3 个可能的表达式可以构成用def-color-printer定义的函数的主体:

(defn make-print-expression [env]
(if (contains? env 'color)
`(prn (str ~'color "-colored " ~'to-print))
(if-let [color (resolve 'color)]
`(prn (str @~color "-colored " ~'to-print))
`(prn ~'to-print))))
(defmacro def-color-printer [name pattern]
(list `defn name [pattern]
(make-print-expression &env)))
(def-color-printer print-limes {to-print :limes})
(let [color "green"]
(def-color-printer print-limes-color-with-local {to-print :limes}))
(def color "greyish")
(def-color-printer print-limes-color-with-var {to-print :limes})
(print-limes {:limes "limes!"})
;=> "limes!"
(print-limes-color-with-local {:limes "limes!"})
;=> "green-colored limes!"
(print-limes-color-with-var {:limes "limes!"})
;=> "greyish-colored limes!"

我还写了一篇关于 Clojure 引用的博客,以防语法引用语法不熟悉。

resolve函数可能会帮助你解决这个问题。它返回符号在当前命名空间中表示的内容,或者返回 nil。您可以将其提供给 if 表达式:

user> (resolve 'asdf)
nil
user> (if (resolve 'asdf) :defined :not-defined)
:not-defined

请记住引用要在测试中解析的符号。

最新更新