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

--~--~---------~--~----~------------~-------~--~----~
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