I am new to Clojure.
I have used to change value of identifier using swap! and reset!.
reset!
(def item (atom "Apple"))
user=> #item
Out Put ;;=> "Apple"
(reset! item "Grapes")
user=> #item
Out Put ;;=> "Grapes"
swap!
(def item (atom "Apple"))
user=> #item
Out Put ;;=> "Apple"
(swap! item (#(str %) "PineApple"))
Out Put ;;=> ClassCastException java.lang.String cannot be cast to clojure.lang.IFn
How can I change value of item by using swap!?
(swap! item (fn [old] "PineApple"))
or:
(swap! item (fn [_] "PineApple"))
But as you are discarding the input, reset! is better here:
(reset! item "PineApple")
As per swap! syntax (swap! atom f). swap should require function.
So I just tried to solve the issue in this way.
(swap! item (fn[s] "Banana"))
Output ;;=> Banana
Related
Given I have this action to perform
(def structure (atom [{:id "an-id"} {:id "another-id"}]))
(def job {:type "some-job"})
(reset! structure (map #(if (= "an-id" (:id %)) (update-in % [:performed-jobs] (fnil conj []) job) %) #structure))
next structure:
[{:id "an-id" :performed-jobs [{:type "some-job"}]} {:id "another-id"}]
How can I use swap! to change a single occurrence in my structure instead of resetting it all?
Replace reset! by swap! by giving it a function that takes the old value of the atom and returns a new value to store in the atom.
Replace dereferencing of the atom with the function's argument, the old value.
(swap! structure
(fn [old]
(map #(if (= "an-id" (:id %))
(update-in % [:performed-jobs]
(fnil conj []) job) %)
old)))
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...
I have an atom called app-state that holds a map. It looks like this:
{:skills [{:id 1 :text "hi"} {:id 2 :text "yeah"}]}
What is the idiomatic way to remove the element inside the vector with :id = 2 ? The result would look like:
{:skills [{:id 1 :text "hi"}]}
...
So far, I have this:
(defn new-list [id]
(remove #(= id (:id %)) (get #app-state :skills)))
swap! app-state assoc :skills (new-list 2)
It works, but I feel like this isn't quite right. I think it could be something like:
swap! app-state update-in [:skills] remove #(= id (:id %))
But this doesn't seem to work.
Any help is much appreciated!
Try this:
(defn new-list [app-state-map id]
(assoc app-state-map :skills (into [] (remove #(= id (:id %)) (:skills app-state-map)))))
(swap! app-state new-list 2)
swap! will pass the current value of the atom to the function you supply it. There's no need to dereference it yourself in the function.
See the docs on swap! for more details.
(swap! state update :skills (partial remove (comp #{2} :id)))
(def skills {:skills [{:id 1 :text "hi"} {:id 2 :text "yeah"}]})
(defn remove-skill [id]
(update skills :skills (fn [sks] (vec (remove #(= id (:id %)) sks)))))
You would then be able to call say (remove-skill 1) and see that only the other one (skill with :id of 2) is left.
I like your way better. And this would need to be adapted for use against an atom.
You can use filter to do this. Here is a function that takes an id and the map and let's you filter out anything that doesn't match your criteria. Of course, you can make the #() reader macro check for equality rather than inequality depending on your needs.
user=> (def foo {:skills [{:id 1 :text "hi"} {:id 2 :text "yeah"}]})
#'user/foo
user=> (defn bar [id sklz] (filter #(not= (:id %) id) (:skills sklz)))
#'user/bar
user=> (bar 1 foo)
({:id 2, :text "yeah"})
user=> (bar 2 foo)
({:id 1, :text "hi"})
Here is a code example from Clojure Programming
(defn character
[name & {:as opts}]
(ref (merge {:name name :itmes #{} :health 500}
opts)))
(def smaug (character "Smaug" :health 500 :strength 400 :items (set (range 50))))
(def bilbo (character "Bilbo" :health 100 :strength 100))
(def gandalf (character "Gandalf" :health 75 :mana 750))
(defn loot
[from to]
(dosync
(when-let [item (first (:items #from))]
(alter to update-in [:items] conj item)
(alter from update-in [:items] disj item))))
(wait-futures 1
(while (loot smaug bilbo))
(while (loot smaug gandalf)))
(map (comp count :items deref) [bilbo gandalf])
(filter (:items #bilbo) (:items #gandalf))
Everything works fine until the last line, which brings up an error:
ClassCastException clojure.lang.PersistentList cannot be cast to clojure.lang.IFn clojure.core/filter/fn--4264 (core.clj:2605)
The constructor function "character" typos the key for :items as :itmes, making the conj on alter get nil as the initial value. Conjing nil and a value gives you a list -- thus the ClassCastException.
Looks like you've got a typo in your definition of character - :itmes instead of :items.
This means that when you loot, you're calling
(conj nil 0), which turns the entry under :items into a list of (0). A list doesn't implement IFn, hence the error.
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)))