When you find yourself thinking these things, reach for a ref. Or
maybe an agent or var, but usually if you have some chunk of data that
will be shared and change over time, you want a ref:
user=> (def employee-records (ref []))
Although if you're dealing with a set of data records, you may
actually want a relation instead of a vector. A relation is just a
set of maps (instead of a vector of maps as your example used).
user=> (def employee-records (ref #{}))
Now proceed as before, except instead of using def to repeatedly
change the root binding of employee-records (which is not idiomatic
Clojure), you use dosync:
user=> (dosync (commute employee-records conj (struct employee "x" 1
"engineer")))
#{{:name "x", :id 1, :role "engineer"}}
user=> (dosync (commute employee-records conj (struct employee "y" 2
"sr. engineer")))
#{{:name "y", :id 2, :role "sr. engineer"} {:name "x", :id 1, :role "engineer"}}
user=> @employee-records
#{{:name "y", :id 2, :role "sr. engineer"} {:name "x", :id 1, :role "engineer"}}
So that's the "ref" part. But now that we're using relations (sets)
we can use the clojure.set functions. For example, we can convert our
un-indexed set of records into a map indexed by name:
user=> (clojure.set/index @employee-records [:name])
{{:name "y"} #{{:name "y", :id 2, :role "sr. engineer"}}, {:name "x"}
#{{:name "x", :id 1, :role "engineer"}}}
It does seem like your change-role example is the sort of thing you'd
want to do with relations. After all, in SQL it'd be something like:
UPDATE employee-records SET role = "manager" WHERE name = "y", right?
But I didn't see much update-type functionality in clojure.set, so
I came up with this:
(defn union [set1 & sets]
(into set1 (concat sets)))
(defn change-all [rel search-map update-map]
(let [idx (clojure.set/index rel (keys search-map))]
(apply conj
(apply union (vals (dissoc idx search-map)))
(map #(merge % update-map) (idx search-map)))))
Yeah, clojure.set/union is pretty picky about the number of args, so I
wrote my own. Anyway, this gives you simple update functionality --
specify your relation, then a map that indicates what records you
want to change, followed by a map with the new key/vals you want.
Like this:
user=> (change-all @employee-records {:name "y"} {:role "manager"})
#{{:name "y", :id 2, :role "manager"} {:name "x", :id 1, :role "engineer"}}
Of course I didn't store that anywhere, so the employeee-records ref
remains unchanged. To update the ref, you'd do this instead:
user=> (dosync (alter employee-records change-all {:name "y"} {:role
"manager"}))
#{{:role "manager", :name "y", :id 2} {:role "engineer", :name "x", :id 1}}
--Chouser
I just renamed these in the wiki, as using underscores in function
names is discouraged in Clojure. If you don't like the new names I
picked, feel free to pick new ones. :-)
--Chouser