I am new to Functional Programming and Clojure, so I am not really sure about what to do for a project at University. The project should show the advantage of Clojure STM for banking transactions (Transfer of money from account A to account B).
So I plan to proceed this way:
define initial data, like a matrix of Refs or something better
generate random operations to execute: [
random-account-source-id(0, N_MAX) ,
random-account-destination-id(0, N_MAX), random-money (0, 1000) ]
insert the transaction into the data structure
Sync transfer of money from source-id to destination-id for all
the insertions in the matrix, such as:
for i=0; i lt N; i++;
synchronize: transfer (matrix[i].source,matrix[i].dest,matrix[i].money)
I'm not sure about this, then, maybe:
(defn do-all[]
(dosync
(when (pos? N)
(transfer (get matrix [pos 1], get matrix [pos 2], get matrix [pos 3])))))
Represent the account with a Ref i.e a Ref for each account and perform the money transfer operation in a dosync operation. Also make sure you dont do any side-effect operation (other than on those Refs) in the dosync operation as it may be retried in case of a conflict while updating refs.
Update:
In case you will have the number of accounts fixed then you could use ref of vectors where each ref in the vector is an account and each account is identified by the index in the vector.
Ex:
(def total-accounts 100)
(def accounts (vec (map (fn [_] (ref 100)) (range total-accounts))))
In case you have to dynamically add new accounts and identify them by name then you can use hash map where key is the account id (unique value) and value is the Ref for account balance. You will need to wrap this map in a Ref in case you want to do concurrent operations for adding/removing accounts from multiple threads.
You might be interested in this example of Clojure's STM used for banking transactions which I posted in response to another question.
Related
I am trying to understand how to set up a clojure pipeline that has multiple outputs per input, but so far I had no luck getting that to work.
The documentation for pipeline states that
[...] the transducer will be applied independently to each
element [...] and may produce zero or more outputs
per input. [...]
However, I fail to understand how to get more than 1 output per input.
I want to apply multiple transformations to the same input and put all results onto the output channel. I am sure this could also be done using mult, tap and merge, however, this introduces much more overhead compared to adding another transformation to a pipeline transducer.
I tried it with a toy example:
(def ca (chan))
(def cb (chan))
(defn f [in] in)
(defn g [in] (* 2 in))
(pipeline 1 cb (map (juxt f g)) ca)
(put! ca 1)
(<!! cb)
However, this outputs [1 2] in a single output instead of two separate outputs.
So: How can I set up a clojure pipeline between two channels such that it produces multiple (>1) outputs on the output channel per input on the input channel?
Use mapcat instead of map. The difference is: map is one to one, while mapcat is one to many.
TL;DR: Is the following a good pattern for a library?
(def ^{:dynamic true} *var*)
(defn my-fn [{:keys [var]}]
(do-smth (or var *var*)))
--
Say I want to write a sentiment analysis library.
Is it good design in get-sentiment fn to accept optional sentiment labels but provide default one as dynamic var?
(def ^:dynamic *sentiment-level-labels*
["Very Negative" "Negative" "Neutral" "Positive" "Very Positive"])
;;...
(defn get-sentiment-scores
"Takes text and gives back a 0 to 4 sentiment score for each sentences."
[text]
;;...)
(defn get-sentiment
"Gives back a sentiment map with sentences scores,
average score, rounded score and labeled score.
Can accepts custom sentiment level labels under :labels opt."
[text & {:keys [labels]}]
(let [scores (get-sentiment-scores text)
average-score (get-average scores)
rounded-score (Math/round average-score)
label (get (or labels *sentiment-level-labels*) rounded-score)]
{:scores scores
:average-score average-score
:rounded-score rounded-score
:label label}))
Clojure library coding standards official page says:
If you present an interface that implicitly passes a parameter via
dynamic binding (e.g. db in sql), also provide an identical interface
but with the parameter passed explicitly.
https://dev.clojure.org/display/community/Library+Coding+Standards
In my example, I provided only one interface but with opt argument.
Is this okay? Are there better ways to handle this?
Thank you!
Dynamic vars are full or pitfalls. They push your API code towards implicit environmental coupling, and often force your calling code to add a lot of (binding ...) clauses, which kind of defeats the purpose of concision for using Dynamic vars in the first place. They also lead to tricky edge cases if control is passed from one thread to another.
In your case, I would recommend simply passing the labels in a params map argument:
(def default-sentiment-level-labels
["Very Negative" "Negative" "Neutral" "Positive" "Very Positive"])
(defn get-sentiment
"Gives back a sentiment map with sentences scores,
average score, rounded score and labeled score.
Can accepts custom sentiment level labels under :labels opt."
[text {:as params, :keys [labels] :or {labels default-sentiment-labels}}]
...))
Note that the usage of a map can be interesting, because a map is opaque to intermediaries: you can have various components of your algorithm read only from the params map the keys that concern them.
Clojure newbie here, I was going through the excellent "Clojure from the ground up" posts, and tried out the last exercise in this post.
When I replace alter with commute, the sum is inaccurate, but I don't understand why.
(def work (ref (apply list (range 1e5))))
(def sum (ref 0))
(defn trans-alter [work sum]
(dosync
(if-let [n (first #work)]
(do
(alter work rest)
(alter sum + n)
(count #work))
0)))
(defn trans-commute [work sum]
(dosync
(if-let [n (first #work)]
(do
(commute work rest)
(commute sum + n)
(count #work))
0)))
(I've skipped the code that sets up the futures and calls them etc)
With trans-alter here I got 4999950000 for the sum (which is the correct expected value), while with trans-commute I got a different value each time, but higher than expected (e.g. 4999998211).
What am I missing here? Thanks in advance!
Commute and alter essentially do the same thing, though commute is a little more lenient on the guarantee of correctness.
Alter instructs the STM to always ensure that this code ran all the way through without any of the refs it uses changing out from under it.
Commute is an instruction to help the STM decide when it needs to abort a transaction because the underlying data changed out from under it.
If everything in a transaction is commutative, then it's ok to let that transaction finish even if some data changed. In your case two transactions could both:
grab the first number
remove the same number from work
add the same number to result
then use commute to instruct the STM that this is OK, and it should just go ahead and commit the transaction anyway...
get the wrong answer.
So in short,the work you are asking to preform is not actually a commutative operation. Specifically removing an item from a list is not commutative. If you change any of the commutes to an alter, then step 4 would have kicked one of them out and only allowed one of them to finish. The one that got kicked out would be re-run on the fresh data and eventually would have arrived at a correct result.
Given that the STM holds a history of say 10 values of refs, agents etc, can those values be read ?
The reason is, I'm updating a load of agents and I need to keep a history of values. If the STM is keeping them already, I'd rather just use them. I can't find functions in the API that look like they read values from the STM history so I guess not, nor can I find any methods in the java source code, but maybe I didn't look right.
You cannot access the stm history of values directly. But you can make use of add-watch to record the history of values:
(def a-history (ref []))
(def a (agent 0))
(add-watch a :my-history
(fn [key ref old new] (alter a-history conj old)))
Every time a is updated (the stm transaction commits) the old value will be conjed onto the sequence that is held in a-history.
If you want to get access to all the intermediary values, even for rolled back transactions you can send the values to an agent during the transaction:
(def r-history (agent [])
(def r (ref 0))
(dosync (alter r
(fn [new-val]
(send r-history conj new-val) ;; record potential new value
(inc r)))) ;; update ref as you like
After the transaction finished, all changes to the agent r-history will be executed.
I am considering using Clojure records to map to changing entities in my program. Are they mutable? Or do you need to use extra refs within the records? I am a little confused about this
It's well worth watching Rich Hickey's fantastic video on identity and state.
Records are designed to be immutable and store the state of something as a value.
To model the state of a changing entity, I'd recommend using a ref that refers to an immutable value that represents the current state. You can use records for the immutable state, but often it's simpler just to use a simple map.
A simple example, where the mutable state is a scoreboard for a game:
; set up map of current scores for each player
(def scores
(ref
{:mary 0
:joe 0 }))
; create a function that increments scores as a side effect
(defn add-score [player amount]
(dosync
(alter scores update-in [player] + amount)))
; add some scores
(add-score :mary (rand-int 10))
(add-score :joe (rand-int 10))
; read the scores
#scores
=> {:mary 6, :joe 1}
I have found that I much more commonly put records in refs than refs in records. mikira's advice to use a simple map sounds very good.
Start with a map and switch to something less flexable when you have to.