意外的功能行为

时间:2016-02-14 02:56:47

标签: clojure

我编写以下函数将s2合并到s1中,并且任何“id”值与s1中任何元素相同的元素都不会被合并。

(defn into-unique-id 
  [s1, [x2 & xs2]]
  (if x2 (if (empty (filter (fn [x] (= (get x "id") (get x2 "id"))) s1)) (into-unique-id (conj s1 x2) xs2) (into-unique-id s1 xs2)) 
         s1))

我在REPL中尝试了以下内容。

gridizela.core=> (def s1 [{"id" "1"} {"id" "2"}])
#'gridizela.core/s1
gridizela.core=> (into-unique-id s1 [{"id" "1"}])
[{"id" "1"} {"id" "2"} {"id" "1"}]

正如您所看到的,结果并不像我预期的那样,不应该添加最后一个元素。

任何帮助都将不胜感激。

1 个答案:

答案 0 :(得分:6)

为了解决您的问题,我必须进行一些重构,我将包含每个版本的代码,以显示获得理想结果所需的步骤。

;; original
(defn into-unique-id 
  [s1, [x2 & xs2]]
  (if x2 (if (empty (filter (fn [x] (= (get x "id") (get x2 "id"))) s1)) (into-unique-id (conj s1 x2) xs2) (into-unique-id s1 xs2)) 
         s1))

原始代码异常布局,使我难以阅读,难以理解。所以我的第一步是应用正常的缩进。

;; indentation
(defn into-unique-id-2
  [s1, [x2 & xs2]]
  (if x2
    (if (empty (filter (fn [x] (= (get x "id")
                                  (get x2 "id")))
                       s1))
      (into-unique-id (conj s1 x2) xs2)
      (into-unique-id s1 xs2)) 
    s1))

当我达到这一点时,我仍然没有完全得到代码,但我看到一些小的更改,这将使它更容易阅读。 cond几乎总是你想要的东西而不是嵌套条件。我使用,,作为空格来区分结果子句与cond中的条件子句。

;; simplification
(defn into-unique-id-3
  [s1, [x2 & xs2]]
  (cond (not x2)
        ,, s1
        (empty (filter (fn [x] (= (get x "id")
                                  (get x2 "id")))
                       s1))
        ,, (into-unique-id (conj s1 x2) xs2)
        :else
        (into-unique-id s1 xs2)))

此时我终于看到了错误:(empty x)将返回任何非零输入的真值,即使是空集合。我们在这里真正想要的是看似相似的({非常不同的)empty?函数。

;; fix -- empty is always truthy here
(defn into-unique-id-4
  [s1, [x2 & xs2]]
  (cond (not x2)
        ,, s1
        (empty? (filter (fn [x] (= (get x "id")
                                   (get x2 "id")))
                        s1))
        ,, (into-unique-id (conj s1 x2) xs2)
        :else
        (into-unique-id s1 xs2)))

接下来我看到我们可以使用内置的filter代替empty? / some

;; simplification -- we have idiomatic functions for this
(defn into-unique-id-5
  [s1, [x2 & xs2]]
  (cond (not x2)
        ,, s1
        (some (fn [x] (= (get x "id")
                         (get x2 "id")))
                        s1)
        ,, (into-unique-id s1 xs2)
        :else
        ,, (into-unique-id (conj s1 x2) xs2)))

早期我注意到这实际上是reduce手动完成的,所以作为最后一步,我将把这个功能显示为减少。

;; structural simplification - we have idiomatic higher order functions
;; that we can use instead of recursion
(defn into-unique-id-6
  [s1, coll]
  (reduce 
   (fn [acc el]
     (if (some (fn [x]
                 (= (get x "id")
                    (get el "id")))
               acc)
       acc
       (conj acc el)))
   s1
   coll))