clojure - if-let syntax - clojure

I am trying to refactor this code to use if-let:
om/IWillMount
(will-mount [_]
(go (while true
(if (om/get-state owner :is-loaded)
(let [updated-world (<! (update-world (:dimensions opts) (:world #data)))]
(om/transact! data #(assoc % :world updated-world))
(swap! app-state assoc :world updated-world))
(let [world (<! (get-world (:dimensions opts)))]
(om/set-state! owner :is-loaded true)
(om/transact! data #(assoc % :world world))
(swap! app-state assoc :world world)))
(<! (timeout (:poll-interval opts))))))
So far I have tried this:
om/IWillMount
(will-mount [_]
(go (while true
(if-let [world (om/get-state owner :is-loaded)]
(<! (update-world (:dimensions opts) (:world #data)))
(<! (get-world (:dimensions opts)))
(om/set-state! owner :is-loaded true)
(om/transact! data #(assoc % :world world))
(swap! app-state assoc :world world))
(<! (timeout (:poll-interval opts))))))
But I get this error:
Caused by: java.lang.IllegalArgumentException: if-let requires 1 or 2
forms after binding vector in web-game-of-life.app:58

if-let is not applicable...
The if-let macro is not applicable here. In an if-let, the result of the binding form is used as the test. It condenses code like
(let [world (get-world ...)]
(if world
(do-something-with world)
(do-something-else)))
if, for example, get-world were to return nil when there was no world retrieved.
In your case value you want to test is different than the value you want to bind. You are testing (om/get-state owner :is-loaded) but not otherwise using the result.
...but duplicate code can be factored out
Your code does have duplication though, so there is a factoring opportunity. First, the same code with the first binding symbol changed to match the second, with the duplicated lines marked.
(if (om/get-state owner :is-loaded)
(let [world (<! (update-world (:dimensions opts) (:world #data)))]
(1) (om/transact! data #(assoc % :world world))
(2) (swap! app-state assoc :world world))
(let [world (<! (get-world (:dimensions opts)))]
(om/set-state! owner :is-loaded true)
(1) (om/transact! data #(assoc % :world world))
(2) (swap! app-state assoc :world world)))
Now factored by inverting the if and the let
(let [world (if (om/get-state owner :is-loaded)
(<! (update-world (:dimensions opts) (:world #data)))
(do
(om/set-state! owner :is-loaded true)
(<! (get-world (:dimensions opts)))))]
(om/transact! data #(assoc % :world world))
(swap! app-state assoc :world world))

if-let, like if expects that the expects either 2 or 3 parameters, where the second is evaluated if the condition is true, and the optional third is evaluated if the condition if false. You can use do to evaluate more than one statements in such cases.

Related

how are swap! and the mmap function working here?

This is a snippet from the Reagent project. Looking at complete-all and clear-done, I understand the point is to swap out the modified map. I don't understand how it's being done. The definition of mmap calls for 3 parameters — and complete-all seems to be calling it with two, namely map and #(assoc-in % [1 :done] v). clear-done calls with remove and #(get-in % [1 :done]). I tried using the repl to experiment but couldn't get the requires to work out.
(ns todomvc.core
(:require [reagent.core :as r]))
(defonce todos (r/atom (sorted-map)))
(defonce counter (r/atom 0))
(defn add-todo [text]
(let [id (swap! counter inc)]
(swap! todos assoc id {:id id :title text :done false})))
(defn toggle [id] (swap! todos update-in [id :done] not))
(defn save [id title] (swap! todos assoc-in [id :title] title))
(defn delete [id] (swap! todos dissoc id))
(defn mmap [m f a] (->> m (f a) (into (empty m))))
(defn complete-all [v] (swap! todos mmap map #(assoc-in % [1 :done] v)))
(defn clear-done [] (swap! todos mmap remove #(get-in % [1 :done])))
The existing map is passed as the first argument to the function. When all else fails...

how to avoid nesting in clojure

when my write a function to check a user can delete a post by clojure,I get this
(defn delete!
{:arglists}
[^String id]
(if (valid-number? id)
(let [result {:code 200 :status "error" :messag "delete success"}]
(if-let [user (session/get :userid)]
(if-let [post (pdb/id id)]
(if (= user (post :user_id))
(do
(pdb/delete! (Long/valueOf id))
(assoc result :status "ok"))
(assoc result :message (emsg :not-own)))
(assoc result :message (emsg :post-id-error))))
(assoc result :message (emsg :not-login)))))
so i want to fix it,i get this
https://github.com/4clojure/4clojure/blob/develop/src/foreclojure/register.clj#L27
https://github.com/4clojure/4clojure/blob/develop/src/foreclojure/utils.clj#L32
but it is line,but not a nest.
the delete! function is nest ugly and it is very hard to understand it,how to write a macro to avoid the nesting a lot.or other way to avoid it.
This doesn't need a macro. I guess cond is a macro, but it is the only one we need to make this code readable.
(defn delete!
;; {:arglists} ; this line will not compile
[^String id]
(let [result {:code 200 :status "error" :message "delete success"}
user (session/get :userid)
post (and user (valid-number? id) (pbd/id id))]
(cond
(not user)
(assoc result :message (emsg :not-login))
(not post)
(assoc result :message (emsg :post-id-error))
(not= user (:user_id post))
(assoc result :message (emsg :not-own))
:else
(do
(pdb/delete! (Long/valueOf id))
(assoc result :status "ok")))))
This is something a lot of people run into, so don't feel bad.
Check out this blog by Christophe Grand, which I think is a pretty nice (and concise!) solution.
Edit: you only need something fancy like this (or alternatively the version using delay in this other post) if you need to short-circuit execution like the original - otherwise noisesmith's answer is the way to go.
Here's how you could do this sort of thing with the Either monad -- I'm sure there are libraries for it already but I'll implement it here for completeness. (Note: this code hasn't been validated.)
(defn success? [v]
(contains? v :success))
(defn inject [v]
{:success v})
(defn bind [v f]
(if (success? v)
(apply f (:success v))
v))
(defmacro >>= [v & body]
(let [binds (map #(list 'bind %) body)]
`(-> ~v ~#binds)))
(defn delete!
{:arglists}
[^String id]
(if (valid-number? id)
(let [result {:code 200 :status "error" :message "delete success"}
check
(>>= (inject {:id id})
#(if-let [user (session/get :userid)]
{:success (assoc % :user user)}
(:failure (assoc result :message (emsg :not-login))))
#(if-let [post (pdb/id (:id %))]
{:success (assoc % :post post)}
{:failure (assoc result :message (emsg :post-id-error))})
#(if (= (:user %) ((:post %) :user_id))
{:success %}
{:failure (assoc result :message (emsg :not-own))}))]
(if (success? check)
(do
(pdb/delete! (Long/valueOf id))
(assoc result :status "ok"))
(:failure check)))))
The >>= macro works like the -> macro (obviously, since it uses it), but if any of the functions return a {:failure ...} then the chain short-circuits (thanks to bind) and the failure value of the function that failed becomes the value returned by >>=.
Edit
I should note that the function I have named inject is actually called return, but I decided to name it inject here since that's more along the lines of what it does in this monad.

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

Race condition with Clojure atom

I apparently do not understand clojure's atom correctly. I thought that it's atomicity guarantee could be demonstrated as follows:
(def users (atom #{}))
(defn add-user! [name]
(swap! users
(fn [users]
(conj users name))))
(do
(map deref
[(future (add-user! "bob"))
(future (add-user! "clair"))
(future (add-user! "ralph"))
(future (add-user! "mark"))
(future (add-user! "bill"))
(future (add-user! "george"))]))
(println #users)
(println
(if (= 5 (count #users))
"SUCCESS!"
"FAIL"))
Unfortunately this is not the case. The code seems to exhibit a race condition on the set contained in the users atom.
Which data structure do I need to use to make sure that all users are successfully added to the users set?
SOLUTION
As pointed out in the comments, there were several bugs in the code. The main bug was not using dorun to force the evaluation of all of the futures. After making this change, the code runs as expected:
(def users (atom #{}))
(defn add-user! [name]
(swap! users
(fn [users]
(conj users name))))
(dorun
(map deref
[(future (add-user! "bob"))
(future (add-user! "clair"))
(future (add-user! "ralph"))
(future (add-user! "mark"))
(future (add-user! "bill"))
(future (add-user! "george"))]))
(println #users)
(println
(if (= 6 (count #users))
"SUCCESS!"
"FAIL"))
See Clojure Atom documentation.
Also from Joy of Clojure:
Atoms are like Refs in that they're synchronous but are like Agents in that they're independent (uncoordinated).

Let over lambda block-scanner in clojure

I have just start reading Let over lambda and I thought I would try and write a clojure version of the block-scanner in the closures chapter.
I have the following so far:
(defn block-scanner [trigger-string]
(let [curr (ref trigger-string) trig trigger-string]
(fn [data]
(doseq [c data]
(if (not (empty? #curr))
(dosync(ref-set curr
(if (= (first #curr) c)
(rest #curr)
trig)))))
(empty? #curr))))
(def sc (block-scanner "jihad"))
This works I think, but I would like know what I did right and what I could do better.
I would not use ref-set but alter because you don't reset the state to a completely new value, but update it to a new value which is obtained from the old one.
(defn block-scanner
[trigger-string]
(let [curr (ref trigger-string)
trig trigger-string]
(fn [data]
(doseq [c data]
(when (seq #curr)
(dosync
(alter curr
#(if (-> % first (= c))
(rest %)
trig)))))
(empty? #curr))))
Then it is not necessary to use refs since you don't have to coordinate changes. Here an atom is a better fit, since it can be changed without all the STM ceremony.
(defn block-scanner
[trigger-string]
(let [curr (atom trigger-string)
trig trigger-string]
(fn [data]
(doseq [c data]
(when (seq #curr)
(swap! curr
#(if (-> % first (= c))
(rest %)
trig))))
(empty? #curr))))
Next I would get rid of the imperative style.
it does more than it should: it traverses all data - even if we found a match already. We should stop early.
it is not thread-safe, since we access the atom multiple times - it might change in between. So we must touch the atom only once. (Although this is probably not interesting in this case, but it's good to make it a habit.)
it's ugly. We can do all the work functionally and just save the state, when we come to a result.
(defn block-scanner
[trigger-string]
(let [state (atom trigger-string)
advance (fn [trigger d]
(when trigger
(condp = d
(first trigger) (next trigger)
; This is maybe a bug in the book. The book code
; matches "foojihad", but not "jijihad".
(first trigger-string) (next trigger-string)
trigger-string)))
update (fn [trigger data]
(if-let [data (seq data)]
(when-let [trigger (advance trigger (first data))]
(recur trigger (rest data)))
trigger))]
(fn [data]
(nil? (swap! state update data)))))