Recently, I've attempted to write a in-memory database program that keeps track of person record. This in-memory database is nothing but a list (in an abstract sense) and each element in the list contains a map of properties that describes a person.
So, we'll have a variable called "db-ref" bounded to an atom of Clojure vector:
(def db-ref (atom []))
We also need a function to create a person containing id, first name, last name and email :
(defn person [pid lname fname email]
{:id pid :lname lname :fname fname :email email})
(Does this remind you of a factory pattern?)
To add a person to the database
(defn add-person [p]
(swap! db-ref conj p))
As you can see, we're adding the parameter "p" (for person) into db-ref using "swap!"
We also need a way to remove a person by their "id". All we have to do is use the same pattern as "add-person", but as oppose to conjoining, we use the remove function (how difficult could this be?):
(defn find-id-p [id element]
(let [k (:id element)]
(if (= id k)
element)))
(defn delete-person [id]
(swap! db-ref (into [] (remove (partial find-id-p id) @db-ref))))
Surely, our test code below should quickly tell us how awesome we are after the first attempt...
(deftest delete-person-test
(testing
(is (= 0 (count-db)))
(add-person (person "100" "fred" "flintstone" "fred@flintstone"))
(add-person (person "101" "barney" "rubble" "barney@rubble"))
(is (= 2 (count-db)))
(delete-person "100")
(is (= 1 (count-db)))
(delete-person "101")
(is (= 0 (count-db)))))
Running "lein test":
ERROR in (delete-person-test) (APersistentVector.java:292)
Uncaught exception, not in assertion.
expected: nil
actual: java.lang.IllegalArgumentException: Key must be integer
at clojure.lang.APersistentVector.invoke (APersistentVector.java:292)
...
...
...
Oopsie! A big fat error telling us something is terribly wrong. Maybe we could sidestep the problem by using reset! function.
(defn delete-person [id]
(reset! db-ref (into [] (remove (partial find-id-p id) @db-ref))))
Re-running the test should tell us we could totally sidestep the issue.
0 failures, 0 errors.
But before we conclude, Clojure documentation says,
(reset! atom newval)
Sets the value of atom to newval without regard for the current value. Returns newval.
Our in-memory database may not work correctly in concurrent environments.
Alright, let's try to revisit the problem. Afterall, we're awesome aren't we?
The swap! function documentation says:
(swap! atom f) (swap! atom f x) (swap! atom f x y)
(swap! atom f x y & args)
Atomically swaps the value of atom to be: (apply f current-value-of-atom args). Note that f may be called multiple times, and thus should be free of side effects. Returns the value that was swapped in.
It looks like we shouldn't dereference "db-ref" when calling on swap.
Below is another take to delete a person using the swap! function:
(defn remove-person [id db-ref]
(remove #(find-id-p id %) db-ref))
(defn delete-person [id]
(swap! db-ref (partial remove-person id)))
We added an extra helper function to call on the actual remove function.
Running the test displays:
No comments:
Post a Comment