I am learning Clojure by implementing a simplistic resource reservation system. At its core are the two functions for placing/canceling reservations for a given resource:
; thread-safe map of {resource -> #{reservations}}
(def known-reservations (atom {}))
(defn place-reservation
[new-res]
(swap! known-reservations
#(merge-with clojure.set/union % {(:resource new-res) #{new-res}})))
(defn cancel-reservation
[canceled-res]
(swap! known-reservations
#(update % (:resource canceled-res) disj canceled-res)))
They seem to work correctly, but the lack of symmetry bothers me. Placing and canceling reservations are symmetric operations, but this is not reflected by the above code: merge-with clojure.set/union
doesn't even resemble update ,,, disj
.
I tried the following variant:
(defn place-reservation
[new-res]
(swap! known-reservations
#(update % (:resource new-res) conj new-res)))
but since conj nil :x
returns a list rather than a set, this leads to duplicate reservations which I want to prevent.
What is the most idiomatic fix to my problem?
PS: if anyone can come up with a more expressive question title, I'll thankfully accept the edit!
edit: here's a simple usage example for experimentation:
(defrecord Resource [name])
(defrecord Reservation [^Resource resource user])
(def car (->Resource "car"))
(def my-res (->Reservation car "me"))
(def your-res (->Reservation car "you"))
(place-reservation my-res)
(place-reservation your-res)
(place-reservation my-res) ;; duplicate reservation must not change anything
(cancel-reservation my-res)