Richelieu: a library for advising functions

283 views
Skip to first unread message

Edwin Watkeys

unread,
Dec 1, 2014, 2:08:54 PM12/1/14
to clo...@googlegroups.com
Hello,

Richelieu, a library for advising functions, is in something resembling announcement-worthy shape. It's available at the following URL:


During my experience writing thunknyc/profile and the associated CIDER support, I realized that advising or decorating functions is something that's been getting reinvented over and over. I wanted to put an end to that. Richelieu supports advising functions as well as vars and namespaces. Multiple advise functions can be associated with a function, and advise functions have access to the underlying var or function this is being decorated. Below is an edited sample from the README that shows how to implement tracing advice using the library.

I hope this may be useful to one or more people out there. I plan on modifying thunknyc/profile to use Richelieu as part of a push to implement additional profiling modalities.

Regards,
Edwin

(require '[richelieu.core :refer [advice advise-ns
                                  *current-advised*
                                  defadvice]])

;;; Here are some simple functions.
(defn add [& xs] (apply + xs))
(defn mult [& xs] (apply * xs))
(defn sum-squares [& xs]
  (apply add (map #(mult % %) xs)))

;;; This tracing advice shows how to get the current advised object,
;;; which can either be a var or a function value, depending on the
;;; context in which the advice was added.
(def ^:dynamic *trace-depth* 0)

(defn- ^:unadvisable trace-indent []
  (apply str (repeat *trace-depth* \space)))

(defadvice trace
  "Writes passed arguments and passes them to underlying
  function. Writes resulting value before returning it as result."
  [f & args] 
  (printf "%s> %s %s\n" (trace-indent) *current-advised* args)
  (let [res (binding [*trace-depth* (inc *trace-depth*)]
              (apply f args))]
    (printf "%s< %s %s\n" (trace-indent) *current-advised* res)
    res))

(advise-ns 'user trace)

(sum-squares 1 2 3 4)
;;; The above invocation produces the following output:

;; > #'user/sum-squares (1 2 3 4)
;;  > #'user/mult (1 1)
;;  < #'user/mult 1
;;  > #'user/mult (2 2)
;;  < #'user/mult 4
;;  > #'user/mult (3 3)
;;  < #'user/mult 9
;;  > #'user/mult (4 4)
;;  < #'user/mult 16
;;  > #'user/add (1 4 9 16)
;;  < #'user/add 30
;; < #'user/sum-squares 30

Phillip Lord

unread,
Dec 2, 2014, 8:11:49 AM12/2/14
to clo...@googlegroups.com

Looks nice. It's pretty similar to Robert Hooke though -- which is more
of an advice library than a hook library despite it's name.
--
Phillip Lord, Phone: +44 (0) 191 208 7827
Lecturer in Bioinformatics, Email: philli...@newcastle.ac.uk
School of Computing Science, http://homepages.cs.ncl.ac.uk/phillip.lord
Room 914 Claremont Tower, skype: russet_apples
Newcastle University, twitter: phillord
NE1 7RU

Edwin Watkeys

unread,
Dec 2, 2014, 8:20:10 AM12/2/14
to clo...@googlegroups.com, Phillip Lord
Phillip,

I’d cry if it weren’t so funny; I’ve just begun to make my way through the lastest Read Eval Print λove and the first page or two dwells on reinvention. At least mine wasn’t intentional.

Edwin

-- 
Edwin Watkeys * 917-324-2435http://poseur.com/


-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/ycw4pZQBFfs/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Phillip Lord

unread,
Dec 2, 2014, 8:39:37 AM12/2/14
to Edwin Watkeys, clo...@googlegroups.com

I think yours might be nicer, to be honest, though, although Robert
Hooke has some features yours doesn't. Advising entire namespaces is an
interesting addition for sure.

I still don't understand why Robert Hooke has this name though. I can't
have been the only person expecting it to implements hooks.

Phil

Edwin Watkeys <e...@poseur.com> writes:

> Phillip,
>
> I’d cry if it weren’t so funny; I’ve just begun to make my way through the
> lastest Read Eval Print λove and the first page or two dwells on reinvention.
> At least mine wasn’t intentional.
>
> Edwin

--

Edwin Watkeys

unread,
Dec 2, 2014, 12:22:28 PM12/2/14
to clo...@googlegroups.com, e...@poseur.com
Phillip,

Of Robert Hooke's features, I think the ability to suppress advice temporarily (its `with-hooks-disabled`) as well to advise a function within a particular dynamic scope (`with-scope`) are most relevant to Richelieu. Since one of the major goals of Richelieu is to serve as a generic basis for advising, I'll probably implement a `with-advice-disabled` form that takes a sequence of advisedf-and-advicef-set values to temporarily suppress.

R.H. and Richelieu, while they do much the same thing, seem to be orthogonal to each other in terms of intent: Richelieu exists to allow folks to write arbitrary advice-based facilities that are oblivious to each others' existences, decorating functions that weren't written with being advised in mind—think tracing and profiling. Phil, on the other hand, focused on providing a facility for developers who anticipate that their code might be advised à la the Emacs hooks mechanism. Or not. I'm totally speculating.

As for the name, I guess I'm willing to overlook some semantic quibbles—especially since something very similar to Emacs's normal hooks could easily built atop R.H.—in pursuit of a charming allusion.

Edwin

Gary Verhaegen

unread,
Dec 2, 2014, 5:01:34 PM12/2/14
to clo...@googlegroups.com
At a very superficial glance, it looks like dire also sort of fits into the same space: https://github.com/MichaelDrogalis/dire
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.

Atamert Ölçgen

unread,
Dec 2, 2014, 8:53:08 PM12/2/14
to clo...@googlegroups.com
Just a small suggestion; I would make :unadvisable namespaced since it's richelieu specific.
--
Kind Regards,
Atamert Ölçgen

-+-
--+
+++

www.muhuk.com

Edwin Watkeys

unread,
Dec 2, 2014, 9:39:45 PM12/2/14
to clo...@googlegroups.com
I think you're right; I was going back and forth on that.

-- 
Edwin Watkeys, 917-324-2435

You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/ycw4pZQBFfs/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.

Edwin Watkeys

unread,
Dec 4, 2014, 9:38:33 AM12/4/14
to clo...@googlegroups.com
Good morning,

Based on feedback from folks, changes since the initial 0.2.1 announcement:

* Added the ability to suppress zero or more advice functions via a `without-advice` macro.
* Changed the keyword used to indicate that a function should never be advised to be namespaced.

https://github.com/thunknyc/richelieu

Below is the example code from the README.

(require '[richelieu.core :refer :all])

;;; Here are some simple functions.

(defn add [& xs] (apply + xs))
(defn mult [& xs] (apply * xs))
(defn sum-squares [& xs]
  (apply add (map #(mult % %) xs)))

;;; `defadvice` is just a way to use `defn` with ':richelieu/no-advice`
;;; metadata to prevent crazy infinite advice loops.

(defadvice plus1
  "Adds one to each incoming argument, does nothing to the output."
  [f & xs]
  (apply f (map inc xs)))

(defadvice times2
  "Multiplies each incoming argument by two, does nothing to the
   output."
  [f & xs]
  (apply f (map (partial * 2) xs)))

;;; You can advise raw functions.

(def add* (-> add
              (advise plus1)
              (advise times2)))

(add* 1 1)

;;; But more often, you'll want to trace vars, which is what the rest
;;; of the example deals with.

;;; This tracing advice shows how to get the current advised object,
;;; which can either be a var or a function value, depending on the
;;; context in which the advice was added.

(def ^:dynamic *trace-depth* 0
)

(defn- ^:richelieu.core/no-advice trace-indent []
  (apply str (repeat *trace-depth* \space)))

(defadvice trace
  
"Writes passed arguments and passes them to underlying
  function. Writes resulting value before returning it as result."
  [f & args] 
  (printf "%s> %s %s\n" (trace-indent) *current-advised* args)
  (let [res (binding [*trace-depth* (inc *trace-depth*)]
              (apply f args))]
    (printf "%s< %s %s\n"
 (trace-indent) *current-advised* res)
    res))

(advise-var #'add trace)
(unadvise-var #'add trace)

;;; This is safe because we used `defadvice` to prevent trace from
;;; advising itself--or other advice functions.

(advise-ns 'user trace)

(sum-squares 1 2 3 4)
;;; The above invocation produces the following output:

;; > #'user/sum-squares (1 2 3 4)
;;  > #'user/mult (1 1)
;;  < #'user/mult 1
;;  > #'user/mult (2 2)
;;  < #'user/mult 4
;;  > #'user/mult (3 3)
;;  < #'user/mult 9
;;  > #'user/mult (4 4)
;;  < #'user/mult 16
;;  > #'user/add (1 4 9 16)
;;  < #'user/add 30
;; < #'user/sum-squares 30

;;; You can also suppress the evalutation of advice with the
;;; `without-advice` macro. For example, the following will produce
;;; no tracing output, but will allow any other advice that (possibly
;;; someone else) attached to any function.

(without-advice [trace] (sum-squares (1 2 3 4))) ;; ==> 30, no tracing

;;; Finally, it will often be a good idea to refer to advice functions
;;; via var quoting instead of by a simple reference. This will allow
;;; you to redefine them during development and still add or remove old
;;; versions of attached advice functions, because they will be
;;; associated with the var, not the particular function value that the
;;; var pointed to at the time.

(advise-ns 'user trace)   ;; This works great until you re-eval
                          ;; your `(defadvice trace ...)` form.

(advise-ns 'user #'trace) ;; Infinitesimally slower but highly
                          ;; recommended.

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.



--
Kind Regards,
Atamert Ölçgen

-+-
--+
+++

www.muhuk.com

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/ycw4pZQBFfs/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+unsubscribe@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages