Clojure中解构命令的惯用方式

4

这是我写的一些代码,使用 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宏内部,从任何地方访问fromto字段都极其难以阅读和容易出错。例如,要从大多数操作向量中提取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"?换句话说,我是否漏掉了什么? - birdspider
@birdspider 谢谢,condp 可能正是我需要的!我只使用 Clojure 几天,所以我写了“在 Clojure 中使用 Scheme”。发表一个答案吧!顺便说一句,在我的例子中,所有记录都具有相同的两个参数。通常情况下,当我做这种事情时,它们是不同的。我现在会修改示例,因为我对如何解构变体记录的习惯用法很感兴趣——但在我的实际代码中,我将像你建议的那样利用共同模式。 :) - Ben Kovitz
1个回答

0

我会利用Clojure内置的解构工具,因为我在这里没有看到核心匹配的要求 - 但我可能会漏掉一些东西。

例如:

(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"

谢谢,这可能正是这个Clojure新手所需要的。三个问题:1)什么是“ksp风格”?(这个?)2)如果不同的操作具有不同的参数,是否有一种简单的方法来解构它们?3)在代码的其他地方通常如何访问动作的“from”和“to”字段? (action 1)(action 2),总是解构,最好使用映射而不是向量,还是其他什么? - Ben Kovitz
@BenKovitz 1) 是的!我只是在测试ksp 1.1预发布版和你的提问有关提升的问题 - 忍不住了。2) 好吧,你可以在(map)键上解构(请参见我提供的链接-向下滚动),3) 那么从可读性的角度来看,为什么不用(let [ [a f t] action ] - 但我想没有硬性规定(除了性能,那么你需要defrecord)。 - birdspider

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接