Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to do hooks in Clojure

Tags:

clojure

hook

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.

like image 536
Phil Lord Avatar asked Nov 03 '22 10:11

Phil Lord


1 Answers

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>
   ()
like image 123
mobyte Avatar answered Nov 09 '22 09:11

mobyte