【问题标题】:Clojure macro: perform action only if a symbol is definedClojure 宏:仅在定义符号时执行操作
【发布时间】:2017-06-01 19:19:51
【问题描述】:

我有一个宏 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(该函数始终需要存在)将在宏展开时在编译时进行检查。

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

有什么建议吗?

【问题讨论】:

    标签: clojure macros


    【解决方案1】:

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

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

    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,以防语法引用语法不熟悉。

    【讨论】:

      【解决方案2】:

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

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

      记得在测试中引用你想要解析的符号。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-29
        相关资源
        最近更新 更多