On Aug 25, 9:31 pm, Rich Hickey <[EMAIL PROTECTED]> wrote:
> On Aug 23, 6:13 am, Parth Malwankar <[EMAIL PROTECTED]> wrote:
>
>
>
> > On Aug 23, 12:23 am, Chouser <[EMAIL PROTECTED]> wrote:
>
> > > On Fri, Aug 22, 2008 at 1:10 PM, Parth Malwankar
>
> > > <[EMAIL PROTECTED]> wrote:
>
> > > > 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...]
> > > > 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
>
> > 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.
>
> > > (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)))))
> > > user=> (change-all @employee-records {:name "y"} {:role "manager"})
> > > #{{: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

>
> Rich
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To post to this group, send email to clojure@googlegroups.com
To unsubscribe from this group, send email to [EMAIL PROTECTED]
For more options, visit this group at 
http://groups.google.com/group/clojure?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to