On Aug 26, 12:08 am, Rich Hickey <[EMAIL PROTECTED]> wrote:
> On Aug 25, 1:16 pm, Parth Malwankar <[EMAIL PROTECTED]> wrote:
>
>
>
> > 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 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
>
> 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
-~----------~----~----~----~------~----~------~--~---