这是我写的一些代码,使用 clojure.core.match,它执行了一个相当常见的编程任务。一个函数接受一些“命令”(或“对象”,“记录”或您喜欢称呼它们的任何内容),必须对每种类型执行不同的操作,并且必须解构它们以确定要做什么,不同的命令类型可能需要以不同的方式进行解构:
(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中做到这一点的惯用方式。
以下是我喜欢上面代码的原因:
它非常易读。
编写起来轻松自如。
以下是我不喜欢的地方:
除了在
match
宏内部,从任何地方访问from
和to
字段都极其难以阅读和容易出错。例如,要从大多数操作向量中提取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中,人们已经在没有match
的情况下愉快地处理这种事情多年了。那么在Clojure中,你该如何处理这种事情呢?
(defn action->edits [g [action f t]]
和(condp = action :boost "boosting"
?换句话说,我是否漏掉了什么? - birdspidercondp
可能正是我需要的!我只使用 Clojure 几天,所以我写了“在 Clojure 中使用 Scheme”。发表一个答案吧!顺便说一句,在我的例子中,所有记录都具有相同的两个参数。通常情况下,当我做这种事情时,它们是不同的。我现在会修改示例,因为我对如何解构变体记录的习惯用法很感兴趣——但在我的实际代码中,我将像你建议的那样利用共同模式。 :) - Ben Kovitz