2017-06-01 39 views
1

我有一个宏defprinter,我可以在其中定义一些函数:基于解构+在打印该变量时获取一个值。Clojure宏:只有在定义符号时才执行动作

这看起来像:

(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))))) 

在我的理解功能的宏将写会尽量EVAL在运行时的表情,失败,一个RuntimeException如果color没有定义,然后执行(prn to-print)。即使它会在运行时检查color的存在,to-print(它始终需要存在该函数)将在宏扩展时在编译时检查。

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

有什么建议吗?

回答

0

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

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

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

2

首先,将您正在使用的两个关注点分开:定义一个函数,并根据可用的变量/本地变量确定如何打印。尽可能简化宏,可以更容易地弄清楚发生了什么。

在决定要打印的内容,你真的有三种情况:

  1. color是本地
  2. color是VAR(或类,我猜?)
  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!" 

我也写了a blog about Clojure's quoting的情况下,语法引用语法不熟悉。