Chiron
Chiron

Reputation: 20245

Aspect-Oriented Programming in Clojure

How to achieve Aspect-Oriented Programming in Clojure? Do we need AOP in Clojure?
Let's say we want plain vanilla Clojure solution (no AspectJ).

Upvotes: 18

Views: 3911

Answers (4)

dqc
dqc

Reputation: 476

Well you could be more AOP w/ Clojure easily. Just use metadata in functions to informe when you want logs:

(defn ^:log my-calculation 
  [a b]
  (+ a b))

Then you can redefine all functions, wrapping them w/ logging automatically. Part of this code (together w/ unwrap functions bellow):

(defn logfn
  [f topic severity error-severity]
  (fn [& args]
    (try
      (if severity
        (let [r (apply f args)]
          (log* topic {:args args, :ret r} severity)
          r)
        (apply f args))
      (catch Exception e
        (if error-severity
          (let [data {:args args, :error (treat-error e), :severity error-severity}]
            (log* topic data error-severity)
            (throw e))
          (throw e))))))

(defn logfn-ns
  "Wrap function calls for logging on call or on error.

  By default, do nothing. When any :log or :log-error, enables logging. If ^:log,
  only log on error (default severity error).

  Can customize log severity w/ e.g. ^{:log info} or on error log severity likewise."
  [ns alias]
  (doseq [s (keys (ns-interns ns))
          :let [v (ns-resolve ns s)
                f @v
                log (-> v meta :log)
                log-error (-> v meta :log-error)]
          :when (and (ifn? f)
                     (-> v meta :macro not)
                     (-> v meta :logged not)  ;; make it idempotent
                     (or log log-error))]

    (let [log (if (= log true) nil log)
          log-error (or log-error "error")
          f-with-log (logfn f
                            (str alias "/" s)
                            log
                            log-error)]
      (alter-meta! (intern ns s f-with-log)
                   (fn [x]
                     (-> x
                         (assoc :logged true)
                         (assoc :unlogged @v)))))))

(defn unlogfn-ns
  "Reverts logfn-ns."
  [ns]
  (doseq [s (keys (ns-interns ns))
          :let [v (ns-resolve ns s)]
          :when (-> v meta :logged)]
    (let [f-without-log (-> v meta :unlogged)]
      (alter-meta! (intern ns s f-without-log)
                   (fn [x]
                     (-> x
                         (dissoc :logged)
                         (dissoc :unlogged)))))))

You just call (log/logfn-ns 'my.namespace "some alias") and all is wrapped w/ logging (and some).

PS: My custom logger above have a topic which is "some alias/function name" PS2: Also wrapped w/ try/catch. PS3: Didn't like this so much. Reverted to have explicit logging.

Upvotes: 3

mikera
mikera

Reputation: 106381

Aspect-Oriented Programming is typically used to add cross-cutting functionality to code that would otherwise get hopelessly intertwined with business logic. A great example is logging - you don't really want logging code scattered everywhere in your code base.

You don't really need AOP in Clojure because it's easy to achieve this with other techniques in Clojure.

For example, you can use higher-order functions to "wrap" other functions with cross cutting functionality:

; a simple function - the "business logic"
(defn my-calculation [a b]
  (+ a b))

; higher order function that adds logging to any other function
(defn wrap-with-logging [func]
  (fn [& args]
    (let [result (apply func args)]
      (println "Log result: " result)
      result)))

; create a wrapped version of the original function with logging added
(def my-logged-calculation (wrap-with-logging my-calculation))

(my-logged-calculation 7 9)
=> Log result:  16
=> 16

Upvotes: 27

Arthur Ulfeldt
Arthur Ulfeldt

Reputation: 91577

Aspect oriented programming is a great way to achieve seperation of concernes in Java. Clojure's composable abstractions achieve this very well. See this question also. The topic is covered really well in The Joy Of Clojure.

as for an example of Aspect Oriented Clojure by another name check out the Ring web framework

Upvotes: 10

Joost Diepenmaat
Joost Diepenmaat

Reputation: 17773

AOP IMHO is just an artifact of certain kinds of static programming languages. AFAIKS it's usually just a bunch of non-standard compiler extensions. I've not yet seen any application of AOP that can't be solved better & natively in more dynamic languages. Clojure is certainly dynamic enough, and that's without even considering macros.

I may be wrong, but if so, I'd need to see an actual AOP use case that can't be implemented just as well in pure clojure.

Edit: just to be clear: I refuse to see things like elisp's advice as aspect oriented. In dynamic languages, those are just techniques to be used whenever you need them, with no need for language support other than rebinding of function definitions - which all lisps support anyway.

There's no need to treat them as special - you can easily define your own defadvice-like function in clojure. See for example, compojure's wrap! macro, which is actually deprecated since you generally don't even need it.

Upvotes: 15

Related Questions