Discussion:
idiomatic way of creating a set of records
(too old to reply)
Parth Malwankar
2008-08-22 17:10:27 UTC
Permalink
Based on a recent thread on structures I am curious to know
what might be the idiomatic way of creating something like
a simple employee record system (or similar such system)
using Clojure.

Below is a sample interaction:

user=> (defstruct employee :name :id :role)
#'user/employee

user=> (def employee-records [])
#'user/employee-records

user=> (def employee-records (conj employee-records (struct employee
"x" 1 "engineer")))
#'user/employee-records

user=> employee-records
[{:name "x", :id 1, :role "engineer"}]

user=> (def employee-records (conj employee-records (struct employee
"y" 1 "sr. engineer")))
#'user/employee-records

user=> employee-records
[{:name "x", :id 1, :role "engineer"} {:name "y", :id 1, :role "sr.
engineer"}]

user=> (defn change-role [name new-role rec]
(if (= (:name name))
(assoc rec :role new-role)
rec))
#'user/change-role

user=> (map #(change-role "y" "manager" %) employee-records)
({:name "x", :id 1, :role "manager"} {:name "y", :id 1, :role
"manager"})

user=> (def employee-records (map #(change-role "y" "manager" %)
employee-records))
#'user/employee-records

user=> employee-records
({:name "x", :id 1, :role "manager"} {:name "y", :id 1, :role
"manager"})

So my question is what is the recommended way of creating a system
like this?

Do the functions like "add-employee", "delete-employee" and such
return the
new list of records which we rebind at a top level? Or do we avoid
rebinding
totally and pass around the the reference to the world in our
functions ....
this seems to be getting into monad territory.

In my experience with CL, as structures are mutable we simply do
in place updates (setf).

Whats the approach generally used in Clojure and how does it
scale?

Comments? Recommendations?

Parth
Chouser
2008-08-22 19:23:42 UTC
Permalink
On Fri, Aug 22, 2008 at 1:10 PM, Parth Malwankar
Post by Parth Malwankar
Based on a recent thread on structures I am curious to know
what might be the idiomatic way of creating something like
a simple employee record system (or similar such system)
using Clojure.
[...clip...]
Post by Parth Malwankar
this seems to be getting into monad territory.
In my experience with CL, as structures are mutable we simply do
in place updates (setf).
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
Parth Malwankar
2008-08-23 02:27:54 UTC
Permalink
Post by Chouser
On Fri, Aug 22, 2008 at 1:10 PM, Parth Malwankar
Post by Parth Malwankar
Based on a recent thread on structures I am curious to know
what might be the idiomatic way of creating something like
a simple employee record system (or similar such system)
using Clojure.
[...clip...]
Post by Parth Malwankar
this seems to be getting into monad territory.
In my experience with CL, as structures are mutable we simply do
in place updates (setf).
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
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
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"}}
#{{: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
{{: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
UPDATE employee-records SET role = "manager" WHERE name = "y", right?
But I didn't see much update-type functionality in clojure.set, so
(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.
#{{: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
user=> (dosync (alter employee-records change-all {:name "y"} {:role
"manager"}))
#{{:role "manager", :name "y", :id 2} {:role "engineer", :name "x", :id 1}}
--Chouser
Thanks for the detailed explanation Chouser. This was very
helpful. This should probably be turned into a more detailed
tutorial and put on the wiki so that I will help more Clojure
newbies like me. I will try to do that as time permits.
Frantisek Sodomka
2008-08-23 07:49:07 UTC
Permalink
Adding 'apply into 'union worked for me:

(defn union [set1 & sets]
(into set1 (apply concat sets)))

Frantisek
Post by Chouser
On Fri, Aug 22, 2008 at 1:10 PM, Parth Malwankar
Post by Parth Malwankar
Based on a recent thread on structures I am curious to know
what might be the idiomatic way of creating something like
a simple employee record system (or similar such system)
using Clojure.
[...clip...]
Post by Parth Malwankar
this seems to be getting into monad territory.
In my experience with CL, as structures are mutable we simply do
in place updates (setf).
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
user=3D> (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=3D> (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
user=3D> (dosync (commute employee-records conj (struct employee "x" 1
"engineer")))
#{{:name "x", :id 1, :role "engineer"}}
user=3D> (dosync (commute employee-records conj (struct employee "y" 2
"sr. engineer")))
#{{:name "y", :id 2, :role "sr. engineer"} {:name "x", :id 1, :role =20
"engineer"}}
#{{:name "y", :id 2, :role "sr. engineer"} {:name "x", :id 1, :role =20
"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
{{: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
UPDATE employee-records SET role =3D "manager" WHERE name =3D "y", right?
But I didn't see much update-type functionality in clojure.set, so
(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.
#{{:name "y", :id 2, :role "manager"} {:name "x", :id 1, :role =20
"engineer"}}
Of course I didn't store that anywhere, so the employeee-records ref
user=3D> (dosync (alter employee-records change-all {:name "y"} {:role
"manager"}))
#{{:role "manager", :name "y", :id 2} {:role "engineer", :name "x", :id =
=20
Post by Chouser
1}}
--Chouser
Parth Malwankar
2008-08-23 10:13:46 UTC
Permalink
Post by Chouser
On Fri, Aug 22, 2008 at 1:10 PM, Parth Malwankar
Post by Parth Malwankar
Based on a recent thread on structures I am curious to know
what might be the idiomatic way of creating something like
a simple employee record system (or similar such system)
using Clojure.
[...clip...]
Post by Parth Malwankar
this seems to be getting into monad territory.
In my experience with CL, as structures are mutable we simply do
in place updates (setf).
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
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
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"}}
#{{: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
{{: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
UPDATE employee-records SET role = "manager" WHERE name = "y", right?
But I didn't see much update-type functionality in clojure.set, so
(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.
#{{: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
user=> (dosync (alter employee-records change-all {:name "y"} {:role
"manager"}))
#{{:role "manager", :name "y", :id 2} {:role "engineer", :name "x", :id 1}}
--Chouser
Here is the "employee" example I put together based on what
I think is good style but then I still have lots to learn about
Clojure :). Thanks Chouser for the detailed inputs.

I am thinking of putting this on wiki along with some
notes and interaction. Any further suggestions welcome.

;======== employee.clj ==========

(alias 'set 'clojure.set)

(defstruct employee :name :id :role)

(def employee-records (ref #{}))

;;;===================================
;;; Private Functions: No Side-effects
;;;===================================

(defn- _update-employee-role [n r recs]
(let [rec (first (set/select #(= (:name %) n) recs))
others (set/select #(not (= (:name %) n)) recs)]
(set/union (set [(assoc rec :role r)]) others)))

(defn- _delete-employee-by-name [n recs]
(set/select #(not (= (:name %) n)) @employee-records))

;;;=============================================
;;; Public Function: Update Ref employee-records
;;;=============================================
(defn update-employee-role [n r]
"update the role for employee named n to the new role r"
(dosync
(ref-set employee-records (_update-employee-role n r @employee-
records))))

(defn delete-employee-by-name [n]
"delete employee with name n"
(dosync
(ref-set employee-records
(_delete-employee-by-name n @employee-records))))

(defn add-employee [e]
"add new employee e to employee-records"
(dosync (commute employee-records conj e)))

;;;=========================
;;; initialize employee data
;;;=========================
(add-employee (struct employee "Jack" 0 :Engineer))
(add-employee (struct employee "Jill" 1 :Finance))
(add-employee (struct-map employee :name "Hill" :id 2 :role :Stand))

;======== end employee.clj ==========

I have separated the "pure functions" from the "transaction
based functions" for better style. E.g. its easier to ensure
correctness of pure functions using set of simple test cases.

I have purposely kept the functions simple e.g "delete by
name" so that the approach is not lost in the code.
Post by Chouser
(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)))))
#{{:name "y", :id 2, :role "manager"} {:name "x", :id 1, :role "engineer"}}
Haven't "change-all" in employee.clj as IMHO this caters to a fairly
common requirement and it may be of value to have
this somewhere in clojure-contrib. Thoughts?

Thanks.
Parth
Rich Hickey
2008-08-25 16:31:03 UTC
Permalink
Post by Parth Malwankar
Post by Chouser
On Fri, Aug 22, 2008 at 1:10 PM, Parth Malwankar
Post by Parth Malwankar
Based on a recent thread on structures I am curious to know
what might be the idiomatic way of creating something like
a simple employee record system (or similar such system)
using Clojure.
[...clip...]
Post by Parth Malwankar
this seems to be getting into monad territory.
In my experience with CL, as structures are mutable we simply do
in place updates (setf).
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
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
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"}}
#{{: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
{{: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
UPDATE employee-records SET role = "manager" WHERE name = "y", right?
But I didn't see much update-type functionality in clojure.set, so
(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.
#{{: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
user=> (dosync (alter employee-records change-all {:name "y"} {:role
"manager"}))
#{{:role "manager", :name "y", :id 2} {:role "engineer", :name "x", :id 1}}
--Chouser
Here is the "employee" example I put together based on what
I think is good style but then I still have lots to learn about
Clojure :). Thanks Chouser for the detailed inputs.
I am thinking of putting this on wiki along with some
notes and interaction. Any further suggestions welcome.
;======== employee.clj ==========
(alias 'set 'clojure.set)
(defstruct employee :name :id :role)
(def employee-records (ref #{}))
;;;===================================
;;; Private Functions: No Side-effects
;;;===================================
(defn- _update-employee-role [n r recs]
(let [rec (first (set/select #(= (:name %) n) recs))
others (set/select #(not (= (:name %) n)) recs)]
(set/union (set [(assoc rec :role r)]) others)))
(defn- _delete-employee-by-name [n recs]
;;;=============================================
;;; Public Function: Update Ref employee-records
;;;=============================================
(defn update-employee-role [n r]
"update the role for employee named n to the new role r"
(dosync
records))))
(defn delete-employee-by-name [n]
"delete employee with name n"
(dosync
(ref-set employee-records
(defn add-employee [e]
"add new employee e to employee-records"
(dosync (commute employee-records conj e)))
;;;=========================
;;; initialize employee data
;;;=========================
(add-employee (struct employee "Jack" 0 :Engineer))
(add-employee (struct employee "Jill" 1 :Finance))
(add-employee (struct-map employee :name "Hill" :id 2 :role :Stand))
;======== end employee.clj ==========
I have separated the "pure functions" from the "transaction
based functions" for better style. E.g. its easier to ensure
correctness of pure functions using set of simple test cases.
I have purposely kept the functions simple e.g "delete by
name" so that the approach is not lost in the code.
Post by Chouser
(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)))))
#{{:name "y", :id 2, :role "manager"} {:name "x", :id 1, :role "engineer"}}
Haven't "change-all" in employee.clj as IMHO this caters to a fairly
common requirement and it may be of value to have
this somewhere in clojure-contrib. Thoughts?
I think you can and should go further still with the functional part
of this.

Try writing all of your logic without refs at all, i.e. as functions
that take the employees 'db' (set) as the first argument, and return
the new set.

Rich
Parth Malwankar
2008-08-25 17:16:44 UTC
Permalink
Post by Rich Hickey
Post by Parth Malwankar
Post by Chouser
On Fri, Aug 22, 2008 at 1:10 PM, Parth Malwankar
Post by Parth Malwankar
Based on a recent thread on structures I am curious to know
what might be the idiomatic way of creating something like
a simple employee record system (or similar such system)
using Clojure.
[...clip...]
Post by Parth Malwankar
this seems to be getting into monad territory.
In my experience with CL, as structures are mutable we simply do
in place updates (setf).
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
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
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"}}
#{{: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
{{: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
UPDATE employee-records SET role = "manager" WHERE name = "y", right?
But I didn't see much update-type functionality in clojure.set, so
(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.
#{{: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
user=> (dosync (alter employee-records change-all {:name "y"} {:role
"manager"}))
#{{:role "manager", :name "y", :id 2} {:role "engineer", :name "x", :id 1}}
--Chouser
Here is the "employee" example I put together based on what
I think is good style but then I still have lots to learn about
Clojure :). Thanks Chouser for the detailed inputs.
I am thinking of putting this on wiki along with some
notes and interaction. Any further suggestions welcome.
;======== employee.clj ==========
(alias 'set 'clojure.set)
(defstruct employee :name :id :role)
(def employee-records (ref #{}))
;;;===================================
;;; Private Functions: No Side-effects
;;;===================================
(defn- _update-employee-role [n r recs]
  (let [rec    (first (set/select #(= (:name %) n) recs))
        others (set/select #(not (= (:name %) n)) recs)]
    (set/union (set [(assoc rec :role r)]) others)))
(defn- _delete-employee-by-name [n recs]
;;;=============================================
;;; Public Function: Update Ref employee-records
;;;=============================================
(defn update-employee-role [n r]
  "update the role for employee named n to the new role r"
  (dosync
records))))
(defn delete-employee-by-name [n]
  "delete employee with name n"
  (dosync
    (ref-set employee-records
(defn add-employee [e]
  "add new employee e to employee-records"
  (dosync (commute employee-records conj e)))
;;;=========================
;;; initialize employee data
;;;=========================
(add-employee (struct employee "Jack" 0 :Engineer))
(add-employee (struct employee "Jill" 1 :Finance))
(add-employee (struct-map employee :name "Hill" :id 2 :role :Stand))
;======== end employee.clj ==========
I have separated the "pure functions" from the "transaction
based functions" for better style. E.g. its easier to ensure
correctness of pure functions using set of simple test cases.
I have purposely kept the functions simple e.g "delete by
name" so that the approach is not lost in the code.
Post by Chouser
(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)))))
#{{:name "y", :id 2, :role "manager"} {:name "x", :id 1, :role "engineer"}}
Haven't "change-all" in employee.clj as IMHO this caters to a fairly
common requirement and it may be of value to have
this somewhere in clojure-contrib. Thoughts?
I think you can and should go further still with the functional part
of this.
Try writing all of your logic without refs at all, i.e. as functions
that take the employees 'db' (set) as the first argument, and return
the new set.
Rich,

I have isolated the "pure" part of the logic in the private functions
in the listing,

(defn- _update-employee-role [n r recs] ...
(defn- _delete-employee-by-name [n recs] ...

Please let me know in case you mean something different. Thanks.

I have put this as a section on the wiki.
http://en.wikibooks.org/wiki/Clojure_Programming#Employee_Record_Manipulation
Comments welcome.

Parth
Post by Rich Hickey
Rich
Chouser
2008-08-25 18:33:09 UTC
Permalink
On Mon, Aug 25, 2008 at 1:16 PM, Parth Malwankar
Post by Parth Malwankar
(defn- _update-employee-role [n r recs] ...
(defn- _delete-employee-by-name [n recs] ...
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
Rich Hickey
2008-08-25 19:08:38 UTC
Permalink
Post by Parth Malwankar
Post by Rich Hickey
Post by Parth Malwankar
Post by Chouser
On Fri, Aug 22, 2008 at 1:10 PM, Parth Malwankar
Post by Parth Malwankar
Based on a recent thread on structures I am curious to know
what might be the idiomatic way of creating something like
a simple employee record system (or similar such system)
using Clojure.
[...clip...]
Post by Parth Malwankar
this seems to be getting into monad territory.
In my experience with CL, as structures are mutable we simply do
in place updates (setf).
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
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
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"}}
#{{: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
{{: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
UPDATE employee-records SET role = "manager" WHERE name = "y", right?
But I didn't see much update-type functionality in clojure.set, so
(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.
#{{: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
user=> (dosync (alter employee-records change-all {:name "y"} {:role
"manager"}))
#{{:role "manager", :name "y", :id 2} {:role "engineer", :name "x", :id 1}}
--Chouser
Here is the "employee" example I put together based on what
I think is good style but then I still have lots to learn about
Clojure :). Thanks Chouser for the detailed inputs.
I am thinking of putting this on wiki along with some
notes and interaction. Any further suggestions welcome.
;======== employee.clj ==========
(alias 'set 'clojure.set)
(defstruct employee :name :id :role)
(def employee-records (ref #{}))
;;;===================================
;;; Private Functions: No Side-effects
;;;===================================
(defn- _update-employee-role [n r recs]
(let [rec (first (set/select #(= (:name %) n) recs))
others (set/select #(not (= (:name %) n)) recs)]
(set/union (set [(assoc rec :role r)]) others)))
(defn- _delete-employee-by-name [n recs]
;;;=============================================
;;; Public Function: Update Ref employee-records
;;;=============================================
(defn update-employee-role [n r]
"update the role for employee named n to the new role r"
(dosync
records))))
(defn delete-employee-by-name [n]
"delete employee with name n"
(dosync
(ref-set employee-records
(defn add-employee [e]
"add new employee e to employee-records"
(dosync (commute employee-records conj e)))
;;;=========================
;;; initialize employee data
;;;=========================
(add-employee (struct employee "Jack" 0 :Engineer))
(add-employee (struct employee "Jill" 1 :Finance))
(add-employee (struct-map employee :name "Hill" :id 2 :role :Stand))
;======== end employee.clj ==========
I have separated the "pure functions" from the "transaction
based functions" for better style. E.g. its easier to ensure
correctness of pure functions using set of simple test cases.
I have purposely kept the functions simple e.g "delete by
name" so that the approach is not lost in the code.
Post by Chouser
(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)))))
#{{:name "y", :id 2, :role "manager"} {:name "x", :id 1, :role "engineer"}}
Haven't "change-all" in employee.clj as IMHO this caters to a fairly
common requirement and it may be of value to have
this somewhere in clojure-contrib. Thoughts?
I think you can and should go further still with the functional part
of this.
Try writing all of your logic without refs at all, i.e. as functions
that take the employees 'db' (set) as the first argument, and return
the new set.
Rich,
I have isolated the "pure" part of the logic in the private functions
in the listing,
(defn- _update-employee-role [n r recs] ...
(defn- _delete-employee-by-name [n recs] ...
Please let me know in case you mean something different. Thanks.
I saw that - I don't think it's enough. Please try what I said, making
all of update-employee-role, delete-employee-by-name, add-employee etc
ref-free, taking the db as first arg and returning the new db. These
should be the public functions and the real interface. Then, if
someone wants to stick the db in a ref, they can, but they don't have
to, and there's no magic global state to the library.

Rich
Parth Malwankar
2008-08-26 05:02:28 UTC
Permalink
Post by Rich Hickey
Post by Parth Malwankar
Post by Rich Hickey
Post by Parth Malwankar
Post by Chouser
On Fri, Aug 22, 2008 at 1:10 PM, Parth Malwankar
Post by Parth Malwankar
Based on a recent thread on structures I am curious to know
what might be the idiomatic way of creating something like
a simple employee record system (or similar such system)
using Clojure.
[...clip...]
Post by Parth Malwankar
this seems to be getting into monad territory.
In my experience with CL, as structures are mutable we simply do
in place updates (setf).
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
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
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"}}
#{{: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
{{: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
UPDATE employee-records SET role = "manager" WHERE name = "y", right?
But I didn't see much update-type functionality in clojure.set, so
(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.
#{{: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
user=> (dosync (alter employee-records change-all {:name "y"} {:role
"manager"}))
#{{:role "manager", :name "y", :id 2} {:role "engineer", :name "x", :id 1}}
--Chouser
Here is the "employee" example I put together based on what
I think is good style but then I still have lots to learn about
Clojure :). Thanks Chouser for the detailed inputs.
I am thinking of putting this on wiki along with some
notes and interaction. Any further suggestions welcome.
;======== employee.clj ==========
(alias 'set 'clojure.set)
(defstruct employee :name :id :role)
(def employee-records (ref #{}))
;;;===================================
;;; Private Functions: No Side-effects
;;;===================================
(defn- _update-employee-role [n r recs]
  (let [rec    (first (set/select #(= (:name %) n) recs))
        others (set/select #(not (= (:name %) n)) recs)]
    (set/union (set [(assoc rec :role r)]) others)))
(defn- _delete-employee-by-name [n recs]
;;;=============================================
;;; Public Function: Update Ref employee-records
;;;=============================================
(defn update-employee-role [n r]
  "update the role for employee named n to the new role r"
  (dosync
records))))
(defn delete-employee-by-name [n]
  "delete employee with name n"
  (dosync
    (ref-set employee-records
(defn add-employee [e]
  "add new employee e to employee-records"
  (dosync (commute employee-records conj e)))
;;;=========================
;;; initialize employee data
;;;=========================
(add-employee (struct employee "Jack" 0 :Engineer))
(add-employee (struct employee "Jill" 1 :Finance))
(add-employee (struct-map employee :name "Hill" :id 2 :role :Stand))
;======== end employee.clj ==========
I have separated the "pure functions" from the "transaction
based functions" for better style. E.g. its easier to ensure
correctness of pure functions using set of simple test cases.
I have purposely kept the functions simple e.g "delete by
name" so that the approach is not lost in the code.
Post by Chouser
(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)))))
#{{:name "y", :id 2, :role "manager"} {:name "x", :id 1, :role "engineer"}}
Haven't "change-all" in employee.clj as IMHO this caters to a fairly
common requirement and it may be of value to have
this somewhere in clojure-contrib. Thoughts?
I think you can and should go further still with the functional part
of this.
Try writing all of your logic without refs at all, i.e. as functions
that take the employees 'db' (set) as the first argument, and return
the new set.
Rich,
I have isolated the "pure" part of the logic in the private functions
 in the listing,
(defn- _update-employee-role [n r recs] ...
(defn- _delete-employee-by-name [n recs] ...
Please let me know in case you mean something different. Thanks.
I saw that - I don't think it's enough. Please try what I said, making
all of update-employee-role, delete-employee-by-name, add-employee etc
ref-free, taking the db as first arg and returning the new db. These
should be the public functions and the real interface. Then, if
someone wants to stick the db in a ref, they can, but they don't have
to, and there's no magic global state to the library.
Oh. I get it, you are thinking of this more like a library (so we have
the employee.clj lib and a separate end user app.clj) while
I was looking at this as an end app. Hence the confusion.

I agree. Its nicer to break it up and keep the pure part as
a library and put refs and stuff in a separate file. Thanks
for your inputs. Will update the wiki.

Parth
Post by Rich Hickey
Rich
Continue reading on narkive:
Loading...