重用多元方法的解构

时间:2014-08-27 12:23:26

标签: clojure

有没有办法在多方法中重用多个方法之间的解构?

(defmulti foo (fn [x] (:a x)))
(defmethod foo :1 [{:keys [a b c d e]}] (str a b c d e))
(defmethod foo :2 [a] "")
(defmethod foo :3 [a] "")

现在这是一个简单的例子,但是想象一下我们使用嵌套地图进行了更为复杂的解构,我希望在我的所有defmethods上使用它来获取foo。我该怎么做?

1 个答案:

答案 0 :(得分:6)

实际的解决方案是仅使用每种方法所需的密钥。关于解构的一个重要注意事项是,您不必绑定您正在解构的集合中的每个值。我们假设传递给此多方法的每个地图都包含:a:e的键,但每种方法只需要几个键。你可以这样做:

; note: a keyword can act as a function; :a here is equivalent to (fn [x] (:a x))
(defmulti foo :a)  
(defmethod foo :1 [{:keys [a b c d e]}] (str a b c d e))
(defmethod foo :2 [{:keys [b d]}] (str b d))
(defmethod foo :3 [{:keys [c e a]}] (str a c e))

如果您有一个复杂的嵌套结构,并且想要获取特定值,则可以省略您不需要的密钥,或者根据您的使用情况,在let内绑定函数定义最终可能更容易阅读。史蒂夫·洛什的Caves of Clojure浮现在脑海中 - 在Clojure中从头开始编写一个roguelike文本冒险游戏,他使用嵌套地图来表示游戏的状态。最初,他使用解构来编写一些函数来访问游戏状态"的内部位置。地图,例如:

(defmethod draw-ui :play [ui {{:keys [tiles]} :world :as game} screen]
  ...

但是later,他决定通过将解构转换为let绑定来使这段代码更具可读性:

(defmethod draw-ui :play [ui game screen]
  (let [world (:world game)
        tiles (:tiles world)
        ...

关键是,如果您正在使用深层嵌套的结构并且希望保持代码简单(特别是如果您正在编写多方法,其中多个方法采用与参数相同的结构),您可能希望避免使用解构,只需使用let绑定来抓取您想要的部分。 get-in是一个很好的工具,可以简单地从嵌套集合中获取值。回到Clojure洞穴的例子,如果史蒂夫只需要瓷砖,他就可以做到这样的事情:

(defmethod draw-ui :play [ui game screen]
  (let [tiles (get-in game [:world :tiles])
    ...

就我个人而言,我发现使用{{:keys [tiles]} :world :as game}来修复函数参数要容易得多。


修改

如果确实想要避免为每个多方法重复解构,并且希望每个方法都具有相同的绑定,那么可以编写一个宏:

(defmulti foo :a)

(defmacro deffoomethod [dispatch-val & body]
  `(defmethod foo ~dispatch-val [{:keys [~'a ~'b ~'c ~'d ~'e]}]
     ~@body))

(deffoomethod 1 (str a b c d e))
(deffoomethod 2 (str b d))
(deffoomethod 3 (str a c e))

(foo {:a 1 :b 2 :c 3 :d 4 :e 5})
;=> "12345"

(foo {:a 2 :b \h :d \i})
;=> "hi"

(foo {:a 3 :b \x :c 0 :d \x :e 0})
;=> "300"
但是,我不推荐这种方法,因为它打破了宏观卫生。使用此宏的任何人都必须记住它将符号ae绑定到参数中的相应键,这可能会有问题。