Manipulating Clojure closures? - clojure

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

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 ...)).

Can you count the number of retries in an Clojure STM transaction with an atom?

Can you count the number of retries in an Clojure STM transaction with an atom? I tried it like below and it seems to work:
(def annotatedCounter (agent 0))
(def finishedCounter (agent 0))
(def annotatedSentences (ref {}))
(def corroboratedSentences (ref {}))
(def retryCounter (atom 0))
;; ...
(defn upvote [id]
(dosync
(if (contains? #annotatedSentences id)
(alter annotatedSentences update-in [id :counter] inc)))
(dosync
(if (contains? #annotatedSentences id)
(do
(if (> (get-in #annotatedSentences [id :counter]) 5)
(do
(swap! retryCounter inc)
(alter corroboratedSentences conj {id (get #annotatedSentences id)})
(alter annotatedSentences dissoc annotatedSentences id)
(send annotatedCounter dec)
(send finishedCounter inc)))))))
I added the retryCounter seen above inside the longer of the transactions, to count the total number of transactions. I know agents would only get sent an inc function once in a transaction, regardless of retries, but does this work, to count the number of transactions with an atom?

sequence comprehensions to get run

The below code I have found from a book (Functional Programming Patterns in Scala and Clojure). The for statement uses close-zip? to filter out people outside of the zips and then it generates a greeting to the people who are left. However, I am not quite sure how people should look like as argument for generate-greetings and print-greetings functions?
(def close-zip? #{19123 19103})
(defn generate-greetings [people]
(for [{:keys [name address]} people :when (close-zip? (address :zip-code))]
(str "Hello, " name ", and welcome to the Lambda Bar And Grille!")))
(defn print-greetings [people]
(doseq [{:keys [name address]} people :when (close-zip? (address :zip-code))]
(println (str "Hello, " name ", and welcome to the Lambda Bar And Grille!"))))
They need to be maps with :name and :address keys, like:
{:name "A Person", :address {:zip-code 19103}}
for will take each element from people and assign each one to {:keys [name address]}. This is called destructuring, and it's just a convenience. It's the same as saying:
(for [person people
:let [name (:name person)
address (:address person)]
:when (close-zip? (:zip-code address))]
(str ...))

Clojure:Why is Ref Lost on Assoc

I am working on updating counters in a map ref in Clojure.
(defn increment-key [this key]
(dosync
(let [value (get #this key)]
(if (= value nil)
(alter this assoc key (ref 1))
(alter this assoc key (alter value inc))))))
However, it looks like the alter value inc statement is losing the reference:
(defn -main [& args]
(def my-map (ref {}))
(increment-key my-map "yellow")
(println my-map)
(increment-key my-map "yellow")
(println my-map))
Which prints:
$ lein run
#<Ref#65dcc2a3: {yellow #<Ref#3e0d1329: 1>}>
#<Ref#65dcc2a3: {yellow 2}>
How can I keep the same reference while updating it in this scenario?
You were almost there. Below is the solution, check the last line of increment-key, you just need to alter the value (not alter the key in the map as you were doing, coz that was causing the key to be updated with the alter return value which in you example was 2, remember alter returns the new value of the ref, not the ref itself). Also don't use def inside a def, you should use let (in your -main function)
(defn increment-key [this key]
(dosync
(let [value (get #this key)]
(if (= value nil)
(alter this assoc key (ref 1))
(alter value inc)))))
(defn -main [& args]
(let [my-map (ref {})]
(increment-key my-map "yellow")
(println my-map)
(increment-key my-map "yellow")
(println my-map)))

How to make a record from a sequence of values

I have a simple record definition, for example
(defrecord User [name email place])
What is the best way to make a record having it's values in a sequence
(def my-values ["John" "john#example.com" "Dreamland"])
I hoped for something like
(apply User. my-values)
but that won't work. I ended up doing:
(defn make-user [v]
(User. (nth v 0) (nth v 1) (nth v 2)))
But I'm sensing there is some better way for achieving this...
Warning: works only for literal sequables! (see MihaĊ‚'s comment)
Try this macro:
(defmacro instantiate [klass values]
`(new ~klass ~#values))
If you expand it with:
(macroexpand '(instantiate User ["John" "john#example.com" "Dreamland"]))
you'll get this:
(new User "John" "john#example.com" "Dreamland")
which is basically what you need.
And you can use it for instantiating other record types, or Java classes. Basically, this is just a class constructor that takes a one sequence of parameters instead of many parameters.
the defrecord function creates a compiled class with some immutable fields in it. it's not a proper clojure functions (ie: not a class that implements iFn). If you want to call it's constructor with apply (which expects an iFun) you need to wrap it in an anonymous function so apply will be able to digest it.
(apply #(User. %1 %2 %3 %4) my-values)
it's closer to what you started with though your approach of defining a constructor with a good descriptive name has its own charm :)
from the API:
Note that method bodies are
not closures, the local environment includes only the named fields,
and those fields can be accessed directy.
Writing your own constructor function is probably the way to go. As Arthur Ulfeldt said, you then have a function you can use as a function (e.g. with apply) rather than a Java-interop constructor call.
With your own constructor function you can also do argument validation or supply default arguments. You gain another level of abstraction to work with; you can define make-user to return a hash-map for quick development, and if you later decide to change to records, you can do so without breaking everything. You can write constructors with multiple arities, or that take keyword arguments, or do any number of other things.
(defn- default-user [name]
(str (.toLowerCase name) "#example.com"))
(defn make-user
([name] (make-user name nil nil))
([name place] (make-user name nil place))
([name user place]
(when-not name
(throw (Exception. "Required argument `name` missing/empty.")))
(let [user (or user (default-user name))]
(User. name user place))))
(defn make-user-keyword-args [& {:keys [name user place]}]
(make-user name user place))
(defn make-user-from-hashmap [args]
(apply make-user (map args [:name :user :place])))
user> (apply make-user ["John" "john#example.com" "Somewhere"])
#:user.User{:name "John", :email "john#example.com", :place "Somewhere"}
user> (make-user "John")
#:user.User{:name "John", :email "john#example.com", :place nil}
user> (make-user-keyword-args :place "Somewhere" :name "John")
#:user.User{:name "John", :email "john#example.com", :place "Somewhere"}
user> (make-user-from-hashmap {:user "foo"})
; Evaluation aborted.
; java.lang.Exception: Required argument `name` missing/empty.
One simple thing you can do is to make use of destructuring.
(defn make-user [[name email place]]
(User. name email place))
Then you can just call it like this
(make-user ["John" "John#example.com" "Dreamland"])
Update for Clojure 1.4
defrecord now defines ->User and map->User thus following in Goran's footstaps, one can now
(defmacro instantiate [rec args] `(apply ~(symbol (str "->" rec)) ~args))
which also works with non-literal sequences as in (instantiate User my-values).
Alternatively, along the lines of map->User one can define a function seq->User
(defmacro def-seq-> [rec] `(defn ~(symbol (str "seq->" rec)) [arg#] (apply ~(symbol (str "->" rec)) arg#)))
(def-seq-> User)
which will allow (seq->User my-values).
The idiomatic way to call a Record constructor is with the Clojure symbol ->MyRecord and that works just fine with apply.
(def my-values ["John" "john#example.com" "Dreamland"])
(defrecord User [name email place])
(apply ->User my-values)
; => #my-ns.User{:name "John",
:email "john#example.com",
:place "Dreamland"}