Updating certain values of maps contained in a list - clojure

I have a list of maps which looks something like this:
(def balances ({:name "Steve" :money 1000} {:name "Bill" :money 1000} ...))
I'm trying to write a function that transfers a given amount (let's say 100) of Steves money to Bill and update the data structure:
({:name "Steve" :money 900} {:name "Bill" :money 1100})
I figured that the function should expect the balance as parameter each time and look something like this:
(defn transfer [current-balances amount sender receiver] ... )
How would a function like this look and is this a clever way of managing and updating the account balance? In general my program will take quite a long list of transfers and iteratively apply them to the balance structure. Do I always have to pass the balance structure to the transfer function because of the persistant data structures in Clojure?

The fact that your balances are all contained under a single umbrella data structure, and that structure is a proper value, your transfer function can simply take in the structure describing the current state of the accounts, another describing the change, and produce the new state of accounts. This lets you treat the transfer actions as proper values as well which should help deal with very long lists of transfers :) My only change would be to use a map of balances instead of a list.
bar> (def balances {"Steve" {:money 1000} "Bill" {:money 1000}})
#'bar/balances
bar> (def transfers [["Steve" "Bill" 100] ["Bill" "Steve" 100]
["Steve" "Bill" 10 ] ["Bill" "Steve" 10 ]
["Bill" "Steve" 10 ]])
#'bar/transfers
Then define a simple transfer function that takes one of these and appliese it to the accounts
(defn transfer [balances [from to ammount]]
(-> balances
(update-in [from :money] - ammount)
(update-in [to :money] + ammount)))
this function can be used to directly reduce any sequence of transfers to the state of all accounts:
bar> (reduce transfer balances transfers)
{"Bill" {:money 990}, "Steve" {:money 1010}}
A function that accepts a new transfers from a customer can then use this function to alter the state of whatever you choose to store your bank in (DB, atom, agent, etc.)
bar> (def bank (agent {:current balances :ledger []}))
#'bar/bank
bar> (defn accept-transfers [transfers]
(send bank assoc :current (reduce transfer (:current #bank) transfers)
:ledger (concat transfers (:ledger #bank))))
#'bar/accept-transfers
bar> (accept-transfers transfers)
#<Agent#2eb9bc1: {:current {"Bill" {:money 1000}, "Steve" {:money 1000}}, :ledger []}>
which puts the transfer on the banks queue (and returns that agent which the REPL quickly prints while the transfer may be running) after a moment when we look at the bank wee see all these transfers have been applied.
bar> bank
#<Agent#2eb9bc1: {:current {"Bill" {:money 990}, "Steve" {:money 1010}},
:ledger (["Steve" "Bill" 100] ["Bill" "Steve" 100]
["Steve" "Bill" 10] ["Bill" "Steve" 10]
["Bill" "Steve" 10])}>

Clojure data is immutable, you can't modify it. You need to use STM.
Here is sample code (simplified to the point of question).
(def account1 {:name "Foo" :money (ref 1000)})
(def account2 {:name "Bar" :money (ref 1000)})
(defn transfer
[from to amount]
(dosync
(alter (:money from) - amount)
(alter (:money to) + amount)))
(transfer account1 account2 100)
(println #(:money account1))
(println #(:money account2))
Read more at http://clojure.org/refs, http://clojure.org/atoms and, perhaps, http://clojure.org/agents

Here's my two cents. I hope it'll be helpful too.
(def balances {1 (ref {:name "Steve"
:money 1000})
2 (ref {:name "Bill"
:money 1000})
3 (ref {:name "John"
:money 1000})})
(defn balance [person-id]
((deref (balances person-id)) :money))
(defn credit [balance amount]
(- balance amount))
(defn debet [balance amount]
(+ balance amount))
(defn update-account [person-id operation-fn amount]
(alter (get balances person-id)
#(assoc % :money
(operation-fn (:money %) amount))))
(defn credit-account [person-id amount]
(update-account person-id credit amount))
(defn debet-account [person-id amount]
(update-account person-id debet amount))
(defn transfer [sender-id receiver-id amount]
(if (< (credit (balance sender-id) amount) 0)
{:result :insufficient-fund}
(do (credit-account sender-id amount)
(debet-account receiver-id amount)
{:result :ok})))
test
(defn transaction []
(dosync (transfer 1 2 100)
(transfer 2 3 200)
(transfer 3 1 200)))
(transaction)
-> {:result :ok}
balances
-> {1 #<Ref#b54dba: {:money 1100, :name "Steve"}>,
2 #<Ref#1020230: {:money 900, :name "Bill"}>,
3 #<Ref#1803641: {:money 1000, :name "John"}>}

Related

Is (def m (update-in m ks f & args)) a good practice?

I'm new to the world of clojure and I have a doubt.
I have a nested map such as
(def accounts (hash-map :XYZ (hash-map :balance (hash-map 171000 0 :171018 500 :171025 200)
:statement (hash-map :171018 [{:desc "purchase" :amount 200}
{:desc "deposit" :amount 700}]
:171025 [{:desc "purchase" :amount 300}]))
And I want to update the statements, so I wrote a simple function:
(defn add-statement
[account date desc amount]
(def accounts (update-in accounts [account :statement date] conj {:desc desc :amount amount}))
But I have a feeling that I'm doing that the wrong way...
You would need to change accounts to be mutable if you want to update them. The usual way to do this is by making accounts an atom. Then your function might look like this:
(defn add-statement! [account date desc amount]
(swap! accounts update-in [account :statement date]
(fn [line-items]
(conj (or line-items []) {:desc desc :amount amount}))))
This will add a statement line-item at a new or an existing date. The update-in is the same as yours, except that a line-item for a new date will be put into a vector rather than a list. (conj keeps the type, but it has to know the type: (conj nil :a) gives (:a)).
To turn accounts into an atom:
(def accounts (atom (hash-map ...)))
I notice your balances are not correct anyway. But if you are updating them be sure to do so in the same swap! function.
To answer your question, "Is (def m (update-in m ...)) a good practice?". Definitely not inside a defn. If you are thinking to put a def inside a defn use a let instead. Outside of a defn it would be fine to have a def that updates another def that has a different name, so (def m2 (update-in m1 ...)).

Clojure: Checking equality of values with the same key across two atoms (using add-watch)

Let's say I have an atom:
(def pets (atom { :cats {} :dogs {} }))
I want to watch this atom for changes, so I create a watcher:
(add-watch pets :pet-watcher
(fn [r k os ns]
(when (not= os ns
(println (str "Pets updated: " ns) )))))
Now, if I update my pets atom, I get notified of changes:
(swap! pets assoc-in [:dogs] {:name "Sparky" :color "Brown"})
; Pets updated: {:cats {}, :dogs {:name "Sparky", :color "Brown"}}
But what if I only want to be notified if :cats changes? I get an error when I try to compare properties of deref'd atoms:
; THIS DOES NOT WORK
(add-watch pets :cat-watcher
(fn [r k os ns]
(when (not= (:cats #os) (:cats #ns)
(println (str "Cats updated: " (:cats #ns)) )))))
(swap! pets assoc-in [:cats] {:name "Whiskers" :color "Orange"})
; Error: No protocol method IDeref.-deref defined for type cljs.core/PersistentArrayMap
There must certainly be an elegant way to compare values with the same key across two atoms. How would you update my :cat-watcher to notify me of changes to just :cats?
There is only one atom in your example.
You don't need to dereference os and ns in a watching function since it gets the old and new values of the atom not the atom itself. Just remove the # signs in your code.

How to create multiple agents in parallel (for? or map?) with name and values in CLOJURE?

I'm trying to make a bunch of agents. Individually, one can do:
(def myAgent (agent 3))
But if I want to make a lot of agents, how can I assign both names and values to the agent in an anonymous function? I have this:
(def agents (vec (map agent (range 0 50)) ))
Which makes 50 agents, but none of them have a value. When I try an anonymous function:
(def agents (vec (map (fn [x] (def x (agent 3)) (range 0 50)) ))
It doesn't work. Any help would be greatly appreciated.
creates a map containing 3 agents whose names are the map keys 0, 1, 2 and the map values are the agents with initial value :initial-value
user=> (zipmap (range 3) (repeatedly #(agent :initial-value)))
{0 #object[clojure.lang.Agent 0x31edaa7d {:status :ready, :val :initial-value}],
1 #object[clojure.lang.Agent 0x26adfd2d {:status :ready, :val :initial-value}],
2 #object[clojure.lang.Agent 0x3336e6b6 {:status :ready, :val :initial-value}]}

Manipulating Clojure closures?

I'm new with Clojure and I'm working on creating a simple bank account function that returns a closure. I have figured out how to initialize the variables, but I can't seem to figure out how to manipulate the balance. I would like to create a :withdrawal and :deposit to perform their respective functions, but I can't seem to figure out how.
(defn account
[name initial-balance password]
(fn [a & args]
(condp = a
:name name
:balance initial-balance
:authenticate (= password (first args)))))
The Clojure idioms usually discourage mutable state. When you do want to mutate something like your bank account balance, the usual way to do that would be with a thread-safe atom. Here's an example:
(defn account
[name initial-balance password]
(let [balance (atom initial-balance)]
(fn [a & args]
(case a
:name name
:balance #balance
:deposit (swap! balance + (first args))
:withdraw (swap! balance - (first args))
:authenticate (= password (first args))))))
Here's another approach which still represents the account as a closure but doesn't mutate balance. Instead, deposit and withdraw return a new account closure.
(defn account
[name balance password]
(fn [msg & args]
(case msg
:name name
:balance balance
:deposit (account name (- balance (first args)) password)
:withdraw (account name (+ balance (first args)) password)
:authenticate (= password (first args)))))
Since e.g. (person :deposit 50) returns a new closure rather than the new balance you would need to follow it up with a :balance call/message to see what the resulting balance was.
(def person (account "name" 100 "password"))
(person :deposit 50) ; returns a new closure
;=> #<user$account$fn__85647 user$account$fn__85647#884327>
(person :balance) ; our original `person` still has its original balance
;=> 100
((person :deposit 50) :balance) ; but a new closure can have a new balance
;=> 150

clojure deferred function execution

Ok Here is what i am trying to do
(defn addresses [person-id]
;addresses-retrival )
(defn person [id]
(merge {:addresses (addresses id)} {:name "john"}))
In the above person function i want addresses to be retrieved only on demand , like only when i do
(:addresses (person 10))
and not when
(person 10)
I am not sure if i am going about this right, being new to clojure.
You can use delay.
(defn person [id]
(delay {:addresses (addresses id) :name "john"}))
(person 2) will then return a delayed, without evaluating anything.
To access the content and evaluate the delayed object, use force or deref (or #).
(:addresses #(person 5))
Alternatively, you can put the delay on the address only.
(defn person [id]
{:addresses (delay (addresses id)) :name "john"})
which can be nicer depending on your problem.
It allows to define:
(defn get-address [person]
#(:address person))
Which will get the delayed address and force it.
(Forcing means computing the first time and retrieving the forced result any other times).
At least as far as sequences go, clojure is pretty damned lazy without needed to be told.
Here, modelling your address-retrieval as counting, try:
(defn addresses [person-id]
(iterate #(do (println %) (inc %)) person-id))
(defn person [id]
(merge {:addresses (addresses id)} {:name "john"}))
(def people (map person (range 100)))
So far it won't have printed anything, but if you say:
(doall (take 5 (:addresses (nth people 10))))
Then you should see the printing happen in exactly the cases that need to happen to count up five in the tenth place. I'd imagine that might be the sort of behaviour you want?
So get your address lookup to produce a lazy sequence (map, filter, reduce will all do)
You can return a function from the addresses function which when later called will retrieve the addresses. Something like this:
(defn addresses [person-id]
#(;addresses-retrival))
(defn person [id]
(merge {:addresses ((addresses id))} {:name "john"}))
Note than the addresses function returns an anonymous function (created using #) and the person function calls that anonymous function using an extra pair of parens.
I can suggest something close to what you expect.
; Note the use of anonymouns function. #(addresses id)
(defn person [id]
(merge {:addresses #(addresses id)} {:name "john"}))
; :addresses returns a function. Evaluate it by wrapping it in another set of parans.
((:addresses (person 10)))
Remember that Delays are memoized, so successive calls of your addresses delay will always yield the same address as the first time you derefed the Delay.
(defn addresses [person-id]
{:home (str (rand-int 100) " Cool St.") :work "1243 Boring St."})
(defn person [id]
(merge {:addresses (delay (addresses id))} {:name "john"}))
(let [person1 (person 1)]
(println #(:addresses person1))
(println #(:addresses person1)))
This will print:
{:home 65 Cool St., :work 1243 Boring St.}
{:home 65 Cool St., :work 1243 Boring St.}
Notice how the home address is unchanged on the second deref of the delay.
If you don't want this behavior you need to use a function closure instead.
(defn addresses [person-id]
{:home (str (rand-int 100) " Cool St.") :work "1243 Boring St."})
(defn person [id]
(merge {:addresses (fn [] (addresses id))} {:name "john"}))
(let [person1 (person 1)]
(println ((:addresses person1)))
(println ((:addresses person1))))
This will print:
{:home 16 Cool St., :work 1243 Boring St.}
{:home 31 Cool St., :work 1243 Boring St.}
Notice how the home address was different on the sub-sequent call to the closure.
So, if you're addresses function does side-effect, say fetches addresses from a database. And the persons can change their addresses, and you'd want your code to always have the most recent address, it's something to keep in mind if Delay works for you, or if a function closure would be a better candidate.