处理地图内打包的数据结构的惯用方法?

时间:2014-03-19 17:58:17

标签: clojure

让我们假设我们有一个描述移动球的数据结构。

(def moving-ball {:position [100 100]
                  :velocity [1 3]
                  :acceleration [0 0]})

编写一个解构地图,更新属性并返回新地图的update-fn是不是惯用?像那样:

(defn update-acceleration
  [{:keys [position] :as s} mx my]
   (let 
     [[x y] position
     dx (- mx x)
     dy (- my y)
     dir (normalize [dx dy])]
      (assoc s :acceleration dir)))

或者分开这些功能会更好吗?

(defn direction
 [[x y] mx my]
 (let [dx (- mx x)
       dy (- my y)]
  (normalize [dx dy])))


(defn update-map
 [{:keys [position] :as s} mx my]
 (assoc s :acceleration (direction position mx my)))

第一个耦合到数据结构(因此不能真正重用),但第二个需要更多代码。有没有更好的方法呢?

2 个答案:

答案 0 :(得分:3)

如果我们改进数据呈现给函数的方式,我们可以从地图数据结构的纠缠中有用地提取函数direction。这与重构人员调用introduce (or extract) parameter objectextract method的内容有关。

问题是[x y]坐标呈现的不均匀方式:有时是两个数字,有时是一对。如果我们将它们全部表示为对,我们可以更好地掌握函数的作用。

如果我们对direction函数执行此操作,则会变为......

(defn direction [[x y] [mx my]]
 (let [dx (- mx x)
       dy (- my y)]
  (normalize [dx dy])))

......我们可以减少到......

(defn direction [from to] (normalize (mapv - to from)))

现在我们有了一个我们可以理解的功能。因为它可能会在update-acceleration内以及在其中使用,所以它是可行的。 (与无意义的参数名称相同的功能并不那么令人信服)。

本着同样的精神,我们可以改革update-acceleration

(defn update-acceleration [{:keys [position] :as s} [mx my]]
   (let 
     [[x y] position
     dx (- mx x)
     dy (- my y)
     dir (normalize [dx dy])]
      (assoc s :acceleration dir)))

......减少到......

(defn update-acceleration [s m]
    (assoc s :acceleration (normalize (mapv - m (:position s)))))

......或者,使用direction函数,......

(defn update-acceleration [s m]
  (assoc s :acceleration (direction (:position s) m)))

您可以从任何语言的重构中获益。 Clojure的序列库扩大了效果。 (其他序列库可用:任何Lisp或其他功能语言,Smalltalk,C#,... YMMV)


P.S。

我猜测normalize返回同一方向的单位向量。以下是使用序列函数执行此操作的一种方法:

(defn normalize [v]
  (let [length (->> v
                  (map #(* % %))
                  (reduce +)
                  Math/sqrt)]
    (mapv #(/ % length) v)))

这绝对值得提取。事实上,我很想退出length

(defn length [v]
  (->> v
       (map #(* % %))
       (reduce +)
       Math/sqrt))

...并将normalize定义为......

(defn normalize [v]
  (let [l (length v)] (mapv #(/ % l) v)))

小警告:mapv将在其最短的参数上静默停止,因此您可能需要检查其所有参数的长度是否相同。

答案 1 :(得分:1)

惯用语是使用update-in。见http://clojuredocs.org/clojure_core/clojure.core/update-in

然后

update-in将使用您调用方向的函数。函数update-in获取移动球的旧值,并将其作为更新函数的参数。所以你必须相应地改变方向。