在Clojure中解构命令的习惯方法

时间:2016-04-08 18:31:25

标签: clojure pattern-matching record idioms destructuring

这是我编写的一些代码,使用clojure.core.match执行相当常见的programmng任务。一个函数需要一些"命令" (或"对象","记录"或者你喜欢称之为的任何东西),必须对每种类型做一些不同的事情,并且必须对它们进行解构以弄清楚要做什么,不同的命令类型可能必须以不同方式进行解构:

(defn action->edits [g action]
  "Returns vector of edits needed to perform action in graph g."
  (match action
    [:boost from to]
      [[:add-edge from to 1.0]]
    [:retract from to]
      [[:remove-edge from to]]
    [:normalize from to]       ; a change has just been made to from->to 
      (map (fn [that] [:remove-edge from that])
           (successors-except g from to))
    [:recip-normalize to from] ; a change has just been made to from->to
      []
    [:reduce-to-unofficial from to competitor]
      [[:remove-edge from to] (make-competitive-edge from competitor]))

我主要模仿人们在Scheme中使用pmatch宏的方式。我想知道在Clojure中这样做的惯用方法是什么。

以下是我对上述代码的看法:

  • 它的非常可读。

  • 写作很轻松。

这是我不喜欢的事情:

  • from宏内部的任何位置访问tomatch字段非常难以理解且容易出错。例如,要从大多数动作向量中提取from元素,请编写(action 1)。如果我添加新动作,该代码将会中断,现在它在:recip-normalize上就会中断。

  • match生成的代码效率低下:它通过反复抛出和捕获异常进行搜索。它并不只是生成一个大的嵌套if

我将这些命令表示为地图进行了一些实验,但它似乎变得冗长,命令的名称并不突出,大大降低了可读性:

  (match action
    {:action :boost :from from :to to}
      [{:edit :add-edge :from from :to to :weight 1.0}]
    {:action :retract :from from :to to}
      [{:edit :remove-edge :from from :to to}]
    . . .)

可能未来版本的match会生成更好的代码,但现在生成的代码很差(并且缺乏对记录的支持)表明,在Clojure中,人们多年来一直在愉快地处理这类事情而没有{{ 1}}。那么你是如何在Clojure中做到这一点的呢?

1 个答案:

答案 0 :(得分:0)

我会利用clojure的内置destructuring facilities,因为我在这里看不到core.match的要求 - 但我可能会遗漏一些东西。

例如:

(defn action->edits [g [action from to]]
  (condp = action
    :boost "boosting"
    :retract "retracting"
    :normalize-ksp-style (recur g [:boost from to])
    nil))

(action->edits 2 [:normalize-ksp-style 1 2]) 
;=> "boosting"