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

时间:2017-06-01 19:19:51

标签: clojure macros

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

这看起来像:

(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(该函数始终需要存在)。

但是这里发生的是我总是得到一个RuntimeException,即使定义了color(即使我在eval语句中只留下to-print,它也找不到它但是在捕获工作正常)。似乎符号没有按照我在eval期间的预期得到解决,但我想不出任何其他方法来实现这一点。

有什么建议吗?

2 个答案:

答案 0 :(得分:2)

首先,将你正在使用的两个问题分开是有意义的:定义一个函数,并根据可用的vars / locals确定如何打印。使宏尽可能简单,可以更容易地弄清楚发生了什么。

在决定打印什么时,你确实有3个案例:

  1. color是本地的
  2. color是var(或类,我猜?)
  3. color未定义
  4. &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!"
    

    我还写了a blog about Clojure's quoting以防语法引用语法不熟悉。

答案 1 :(得分:0)

user> (resolve 'asdf) nil user> (if (resolve 'asdf) :defined :not-defined) :not-defined 功能可能会帮助您解决此问题。它返回符号在当前名称空间中表示的内容,或者返回nil。您可以将其提供给if表达式:

x<-1:10
y<-ecdf(x)(x)

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