Clojure:Why is Ref Lost on Assoc - clojure

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

Related

Using swap! instead of reset! in atom - clojure

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

Clojure: swap and reset to change identifier value

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

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 do hooks in Clojure

I have a situation where I am creating and destroying objects in one clojure namespace, and want another namespace to co-ordinate. However I do not want the first namespace to have to call the second explicitly on object destruction.
In Java, I could use a listener. Unfortunately the underlying java libraries do not signal events on object destruction. If I were in Emacs-Lisp, then I'd use hooks which do the trick.
Now, in clojure I am not so sure. I have found the Robert Hooke library https://github.com/technomancy/robert-hooke. But this is more like defadvice in elisp terms -- I am composing functions. More over the documentation says:
"Hooks are meant to extend functions you don't control; if you own the target function there are obviously better ways to change its behaviour."
Sadly, I am not finding it so obvious.
Another possibility would be to use add-watch, but this is marked as alpha.
Am I missing another obvious solution?
Example Added:
So First namespace....
(ns scratch-clj.first
(:require [scratch-clj.another]))
(def listf (ref ()))
(defn add-object []
(dosync
(ref-set listf (conj
#listf (Object.))))
(println listf))
(defn remove-object []
(scratch-clj.another/do-something-useful (first #listf))
(dosync
(ref-set listf (rest #listf)))
(println listf))
(add-object)
(remove-object)
Second namespace
(ns scratch-clj.another)
(defn do-something-useful [object]
(println "object removed is:" object))
The problem here is that scratch-clj.first has to require another and explicitly push removal events across. This is a bit clunky, but also doesn't work if I had "yet-another" namespace, which also wanted to listen.
Hence I thought of hooking the first function.
Is this solution suitable to your requirements?
scratch-clj.first:
(ns scratch-clj.first)
(def listf (atom []))
(def destroy-listeners (atom []))
(def add-listeners (atom []))
(defn add-destroy-listener [f]
(swap! destroy-listeners conj f))
(defn add-add-listener [f]
(swap! add-listeners conj f))
(defn add-object []
(let [o (Object.)]
(doseq [f #add-listeners] (f o))
(swap! listf conj o)
(println #listf)))
(defn remove-object []
(doseq [f #destroy-listeners] (f (first #listf)))
(swap! listf rest)
(println #listf))
Some listeners:
(ns scratch-clj.another
(:require [scratch-clj.first :as fst]))
(defn do-something-useful-on-remove [object]
(println "object removed is:" object))
(defn do-something-useful-on-add [object]
(println "object added is:" object))
Init binds:
(ns scratch-clj.testit
(require [scratch-clj.another :as another]
[scratch-clj.first :as fst]))
(defn add-listeners []
(fst/add-destroy-listener another/do-something-useful-on-remove)
(fst/add-add-listener another/do-something-useful-on-add))
(defn test-it []
(add-listeners)
(fst/add-object)
(fst/remove-object))
test:
(test-it)
=> object added is: #<Object java.lang.Object#c7aaef>
[#<Object java.lang.Object#c7aaef>]
object removed is: #<Object java.lang.Object#c7aaef>
()
It sounds a lot like what you're describing is callbacks.
Something like:
(defn make-object
[destructor-fn]
{:destructor destructor-fn :other-data "data"})
(defn destroy-object
[obj]
((:destructor obj) obj))
; somewhere at the calling code...
user> (defn my-callback [o] (pr [:destroying o]))
#'user/my-callback
user> (destroy-object (make-object my-callback))
[:destroying {:destructor #<user$my_callback user$my_callback#73b8cdd5>, :other-data "data"}]
nil
user>
So, here is my final solution following mobytes suggestion. A bit more work, but
I suspect that I will want this in future.
Thanks for all the help
;; hook system
(defn make-hook []
(atom []))
(defn add-hook [hook func]
(do
(when-not
(some #{func} #hook)
(swap! hook conj func))
#hook))
(defn remove-hook [hook func]
(swap! hook
(partial
remove #{func})))
(defn clear-hook [hook]
(reset! hook []))
(defn run-hook
([hook]
(doseq [func #hook] (func)))
([hook & rest]
(doseq [func #hook] (apply func rest))))
(defn phils-hook []
(println "Phils hook"))
(defn phils-hook2 []
(println "Phils hook2"))
(def test-hook (make-hook))
(add-hook test-hook phils-hook)
(add-hook test-hook phils-hook2)
(run-hook test-hook)
(remove-hook test-hook phils-hook)
(run-hook test-hook)

Clojure error when calling swap! on atom, trying 'conj this-number'

I want a map in an atom that can keep track of times as Unix time stamps.
So, in my main function I have:
(defn -main [& args]
(println "Server is starting")
(def port (Integer/parseInt (first args)))
(def registry (atom {}))
(run-server port who-is-here registry))
And inside of run-server I have a call to add-to-logged-in-registry:
(defn add-to-logged-in-registry
[registry]
(let [moments (Date.)
right-now (.getTime moments)]
(swap! registry conj right-now)))
This last line gives me this error:
Exception in thread "main" java.lang.IllegalArgumentException: Don't know how to create ISeq from: java.lang.Long
at clojure.lang.RT.seqFrom(RT.java:487)
at clojure.lang.RT.seq(RT.java:468)
at clojure.lang.APersistentMap.cons(APersistentMap.java:39)
at clojure.lang.RT.conj(RT.java:544)
at clojure.core$conj.invoke(core.clj:83)
at clojure.lang.Atom.swap(Atom.java:51)
at clojure.core$swap_BANG_.invoke(core.clj:2107)
at who_is_logged_in.core$add_to_logged_in_registry.invoke(core.clj:39)
at who_is_logged_in.core$listen_and_respond.invoke(core.clj:42)
at who_is_logged_in.core$run_server.invoke(core.clj:52)
at who_is_logged_in.core$_main.doInvoke(core.clj:76)
at clojure.lang.RestFn.applyTo(RestFn.java:137)
at who_is_logged_in.core.main(Unknown Source)
What does this mean?
When I try this at the REPL in emacs, this works perfectly:
user> (def registry (atom []))
#'user/registry
user> (let [moments (Date.)
right-now (.getTime moments)]
(swap! registry conj right-now))
[1345698128988]
user> (let [moments (Date.)
right-now (.getTime moments)]
(swap! registry conj right-now))
[1345698128988 1345698132472]
conj behaves differently depending on the type of collection it is adding elements to. In your first example it is adding elements to a map and needs A key and a value in a collection. In your REPL example it is adding elements to a vector and needs only a single value.
swap!ing into a map:
(def registry (atom{}))
(let [moments (java.util.Date.)
right-now (.getTime moments)]
(swap! registry conj [:time right-now]))
{:time 1345700872898}