仅当元素位于集合中时才从集合中删除元素

时间:2016-07-22 14:39:50

标签: clojure

玩家列表被定义为一组:

(def players (atom #{}))

删除播放器的功能应该根据元素是否在集合中返回不同的HTTP代码:

(defn remove-player [player-name]
  (if (contains? @players player-name)
    (do (swap! players disj player-name)
        (status (response "") 200))
    (status (response "") 404)))

此代码的问题在于它可能会向并发请求返回多个200,即使只有一个请求实际删除了该元素。

我想我需要原子地执行contains?disj。我是否需要进行显式锁定或有更好的方法吗?

2 个答案:

答案 0 :(得分:3)

swap!本身是原子操作,所以在swap的计算函数中你可以确定第一个参数(atom的当前值)是一致的。就个人而言,我会为此做一个辅助函数:

(defn remove-existent [value set-a]
  (let [existed (atom false)]
    (swap! set-a
           #(if (contains? % value)
              (do (reset! existed true)
                  (disj % value))
              (do (reset! existed false) 
                  %))
    @existed))

如您所见,lambda表达式包含了一致性检查和删除。

user> (def players (atom #{:user1 :user2}))
#'user/players

user> (remove-existent :user100 players)
false

user> (remove-existent :user1 players)
true

user> @players
#{:user2}

<强>更新

受@ clojuremostly优秀元数据方法的启发,你可以做得更好:

(defn remove-existent [value set-a]
  (-> (swap! set-a #(with-meta (disj % value)
                      {:existed (contains? % value)}))
      meta
      :existed))

答案 1 :(得分:2)

您可以为交换功能添加更多逻辑:

(for [el-rem [:valid-el :not-there]]
  (let [a (atom #{:valid-el :another-one})
        disj-res
        (swap! a
               (fn [a]
                 (with-meta (disj a el-rem)
                            {:before-count (count a)})))]
    [disj-res
     "removed:"
     el-rem
     (not (== (:before-count (meta disj-res))
              (count disj-res)))]))

然后,您将比较返回值disj-res的计数和元数据中的计数。如果它不同,那么disj确实删除了一个元素。如果没有,则该元素不存在。