【问题标题】:Evaluate symbol passed to def in Clojure在 Clojure 中评估传递给 def 的符号
【发布时间】:2017-02-23 15:45:06
【问题描述】:

我正在通过 Clojure 为 Brave and True 工作。在关于宏的章节中有这个练习:

编写一个宏,使用一个宏调用定义任意数量的属性检索函数。你可以这样称呼它:

(defattrs c-int :intelligence
          c-str :strength
          c-dex :dexterity)

这些函数的作用是从映射中检索一个值。例如给定:(def character {:name "Travis", :intelligence 20, :strength 23, :dexterity 13})

(c-int character) 的结果是20 当然这样的函数可以很容易地定义为(def c-int #(:intelligence %))

这是我对这个问题提出的解决方案:

(defmacro defattrs
    [& attributes]
    `(let [attribute-pairs# (partition 2 (quote ~attributes))]
          (map (fn [[function-name# attribute-key#]]
                   (def function-name# #(attribute-key# %)))
           attribute-pairs#)))

我遇到的问题是def 使用生成的符号名称而不是它解析来定义函数的名称(考虑到def 的用法,事后看来这是有道理的)。我尝试使用具有定义函数的表达式,例如:

(let [x ['c-int :intelligence]]
  (def (first x) #((second x) %)))

已导致此错误:CompilerException java.lang.RuntimeException: First argument to def must be a Symbol, compiling:(/tmp/form-init5664727540242288850.clj:2:1)

关于如何实现这一点的任何想法?

【问题讨论】:

    标签: clojure macros


    【解决方案1】:

    您对attributes 参数进行的一些普通操作不需要生成为表单:

    • 将属性拆分为属性对;和
    • 定义函数来为每对生成一个def 表单。

    将上述应用到您的代码中,我们得到...

    (defmacro defattrs [& attributes]
      (let [attribute-pairs (partition 2 attributes)]
         (map (fn [[function-name attribute-key]]
                `(def ~function-name #(~attribute-key %)))
              attribute-pairs)))
    
    • 反引号的范围仅限于我们希望生成的def
    • 函数的function-nameattribute-key参数的插入到def表单中。

    还有一个问题。

    • map 的结果是def 表单的序列。
    • 第一个将被解释为一个函数 适用于其余部分。

    解决方法是将cons一个do放到序列的最前面:

    (defmacro defattrs [& attributes]
      (let [attribute-pairs (partition 2 attributes)]
        (cons 'do
              (map (fn [[function-name attribute-key]]
                     `(def ~function-name ~attribute-key))
                   attribute-pairs))))
    

    我还在反引号表格中将#(~attribute-key %) 缩写为等效的~attribute-key

    让我们看看扩展的样子:

    (macroexpand-1 '(defattrs dooby :brrr))
    ;(do (def dooby :brrr))
    

    看起来不错。让我们试试吧!

    (defattrs gosh :brrr)
    (gosh {:brrr 777})
    ;777
    

    它有效。

    【讨论】:

      【解决方案2】:

      您已经找到了反引号和波浪号的用例。试试这个:

      (let [x ['c-int :intelligence]]
        (eval `(def ~(first x) #(~(second x) %))))
      
      (def character {:name "Travis", :intelligence 20, :strength 23, :dexterity 13})
      
      (c-int character) => 20
      

      反引号类似于单引号,因为它将下一个形式变成了列表、符号等的数据结构。不同之处在于该数据结构旨在用作模板,其中内部可以使用波浪号替换位。很酷的部分是波浪号不仅可以替换项目,还可以用于可以是任意 Clojure 表达式的实时代码。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-05-25
        • 1970-01-01
        • 1970-01-01
        • 2016-05-30
        • 1970-01-01
        • 1970-01-01
        • 2018-05-05
        • 2012-11-19
        相关资源
        最近更新 更多