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).
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
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.
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
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.
Related
I am reading a book to teach myself Clojure called Clojure for the Brave and True. Chapter 9 covers basic concurrent programming including delays, futures, and promises. The first exercise at the end of the chapter states:
"Write a function that takes a string as an argument and searches for it on Bing and Google using the slurp function. Your function should return the HTML of the first page returned by the search"
My solution is as follows:
(defn search-bing-google
[search-term]
(let [search-results (promise)]
(future (deliver search-results
(slurp (str "https://www.bing.com/search?q%3D" search-term))))
(future (deliver search-results
(slurp (str "https://www.google.com/search?q%3D" search-term))))
#search-results))
And can be called like:
(search-bing-google "clojure")
The second exercise is stated as:
"Update your function so it takes a second argument consisting of the search engines to use."
I attempted to edit my first solution to meet the new argument requirement as follows:
(def search-engines
{:bing "https://www.bing.com/"
:google "https://www.google.com/"})
(defn search
[search-term & engines]
(let [results (promise)]
(map #(future (deliver results
(slurp (str (% search-engines)
"search?q%3D"
search-term)))) engines)
#results))
and can be called like:
(search "clojure" :bing :google)
However this implementation hangs unlike it's predecessor. Its as if the slurp never gets invoked because of the "map" in the second implementation. Can anyone help me figure out what's causing this to hang when I load it up in the REPL and execute it?
EDIT:
With Josh's answer below I came up with the following solution that no longer hangs using doseq instead of dorun and a map:
(def search-engines
{:bing "https://www.bing.com/"
:google "https://www.google.com/"})
(defn search
[search-term & engines]
(let [results (promise)]
(doseq [engine engines]
(future (deliver results
(slurp (str (engine search-engines)
"search?q%3D"
search-term)))))
#results))
Because map results in lazy evaluation, something is required in order to realize it. In your code, nothing does this, so the futures are never actually created. Instead of just map, do:
(dorun (map ...
I'd like to mock out a macro in a namespace.
For instance, clojure.tools.logging/error.
I tried with-redefs with no luck
(def logged false)
(defmacro testerror
{:arglists '([message & more] [throwable message & more])}
[& args]
`(def logged true))
(deftest foo
...
(with-redefs
[log/error testerror]
...
That gave this error:
CompilerException java.lang.RuntimeException: Can't take value of a macro
Amalloy provided you the answer for your direct question on how to mock a macro - you cannot.
However, you can solve your problem with other solutions (simpler than moving your whole application to component dependency injection). Let me suggest two alternative implementations (unfortunately, not very straightforward but still simpler than using component).
Mock the function called by logging macro
You cannot mock a macro but you can mock a function that will be used when the logging macro get expanded.
(require '[clojure.tools.logging :as log])
(require '[clojure.pprint :refer [pprint]])
(pprint (macroexpand `(log/error (Exception. "Boom") "There was a failure")))
Gives:
(let*
[logger__739__auto__
(clojure.tools.logging.impl/get-logger
clojure.tools.logging/*logger-factory*
#object[clojure.lang.Namespace 0x2c50fafc "boot.user"])]
(if
(clojure.tools.logging.impl/enabled? logger__739__auto__ :error)
(clojure.core/let
[x__740__auto__ (java.lang.Exception. "Boom")]
(if
(clojure.core/instance? java.lang.Throwable x__740__auto__)
(clojure.tools.logging/log*
logger__739__auto__
:error
x__740__auto__
(clojure.core/print-str "There was a failure"))
(clojure.tools.logging/log*
logger__739__auto__
:error
nil
(clojure.core/print-str x__740__auto__ "There was a failure"))))))
As you can see, the function that does actual logging (if a given level is enabled) is done with clojure.tools.logging/log* function.
We can mock it and write our test:
(require '[clojure.test :refer :all])
(def log-messages (atom []))
(defn log*-mock [logger level throwable message]
(swap! log-messages conj {:logger logger :level level :throwable throwable :message message}))
(with-redefs [clojure.tools.logging/log* log*-mock]
(let [ex (Exception. "Boom")]
(log/error ex "There was a failure")
(let [logged (first #log-messages)]
(is (= :error (:level logged)))
(is (= "There was a failure!" (:message logged)))
(is (= ex (:throwable logged))))))
Use your logging library API to collect and inspect log messages
Your logging library API might provide features that would allow you to plug into in your test to collect and assert logging events. For example with java.util.logging you can write your own implementation of Handler that would collect all logged log records and add it to a specific (or root) logger.
You cannot do this. The point of macros is that they are expanded when the code is compiled, and after that they are gone. The original code that included a call to the macro is unrecoverable. You cannot retroactively redefine a macro at runtime: you're too late already.
An alternative approach, if you want to have swappable logging implementations, would be to use something like Component for dependency injection, and use a different logging component depending on whether you are running tests or running your real program. Arguably that's a bit heavy-handed, and maybe there is a simpler approach, but I don't know it.
I am very new to Clojure. I know there is a way to include files in other files using a keyword like #include for C++. and i have done some digging and found a few things pointing to the keyword declare. Maybe I'm not using it correctly but it doesn't seem to work for me.
I am trying to write a function, for example, get_all_preds that uses another function predecessors, written in another file. the files are both under the directory src and each function is the only code in their respective files.
When i add (declare bar.clj) to the top of the foo.clj file i still get an error that bar cannot be found.
(defn get_all_preds
([tasks job]
(cond (empty? (predecessors tasks job)) (println "No predecessors")
:else (get_all_preds tasks (predecessors tasks job) (empty tasks))))
([tasks prereqs job]
(cond (empty? prereqs) job
:else (get_all_preds tasks (distinct (flatten (concat (predecessors tasks (first prereqs)) (rest prereqs))))
(distinct (cons (first prereqs) job))))))
(defn predecessors
[tasks-list job]
(cond (empty? tasks-list) 0
(= job (first (first tasks-list))) (rest (rest (first tasks-list)))
:else (predecessors (rest tasks-list) job)))
declare is for creating a var without binding it to a value.
The proper function for loading another Clojure namespace is require. To use require, you need to use namespaces, and create files with paths reflecting the semantic structure of the namespaces the files hold. The closest equivalent to #include is load, but this should be reserved for unstructured experiments where you might as well be copy / pasting into the repl, or unusual situations where namespaces are not appropriate.
A quick example:
in file src/foo/bar.clj:
(ns foo.bar)
(defn frob [] "hello")
in file src/foo/baz.clj:
(ns foo.baz
(:require [foo.bar :as bar]))
(defn frob [] (str (bar/frob) (bar/frob)))
(defn -main [& args]
(println (frob))
Running foo.baz/-main will print "hellohello" followed by a new line.
A Clojure namespace is a more structured concept than the compilation units of C, and is something that you can directly interact with in your code in a granular way. It will seem strange at first, but it allows fine grained control of what definitions are visible from various parts of your code (which in turn helps make code much easier to maintain and understand).
I'd like to test whether a collection is transient. I tried the following function:
(defn transient? [coll]
(extends? clojure.lang.ITransientCollection (type coll)))
But it doesn't work:
user=> (transient? {})
NullPointerException clojure.core/implements? (core_deftype.clj:512)
user=> (transient? (transient {}))
NullPointerException clojure.core/implements? (core_deftype.clj:512)
The documentation of extends? doesn't mention that it would work with Java interfaces anyway. I was just hoping they'd work. Is there any other way to determine if a collection is transient?
I'm using Clojure 1.5.1.
You need to use instance? not extends?.
extends? asks whether a type extends a protocol. ITransientCollection is a Java interface, not a protocol, so instance? is the tool for that.
user=> (instance? clojure.lang.ITransientCollection (transient {}))
true
user=> (instance? clojure.lang.ITransientCollection {})
false
After a few weekends exploring Clojure I came up with this program. It allows you to move a little rectangle in a window. Here's the code:
(import java.awt.Color)
(import java.awt.Dimension)
(import java.awt.event.KeyListener)
(import javax.swing.JFrame)
(import javax.swing.JPanel)
(def x (ref 0))
(def y (ref 0))
(def panel
(proxy [JPanel KeyListener] []
(getPreferredSize [] (Dimension. 100 100))
(keyPressed [e]
(let [keyCode (.getKeyCode e)]
(if (== 37 keyCode) (dosync (alter x dec))
(if (== 38 keyCode) (dosync (alter y dec))
(if (== 39 keyCode) (dosync (alter x inc))
(if (== 40 keyCode) (dosync (alter y inc))
(println keyCode)))))))
(keyReleased [e])
(keyTyped [e])))
(doto panel
(.setFocusable true)
(.addKeyListener panel))
(def frame (JFrame. "Test"))
(doto frame
(.add panel)
(.pack)
(.setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
(.setVisible true))
(defn drawRectangle [p]
(doto (.getGraphics p)
(.setColor (java.awt.Color/WHITE))
(.fillRect 0 0 100 100)
(.setColor (java.awt.Color/BLUE))
(.fillRect (* 10 (deref x)) (* 10 (deref y)) 10 10)))
(loop []
(drawRectangle panel)
(Thread/sleep 10)
(recur))
Despite being an experienced C++ programmer I found it very challenging to write even a simple application in a language that uses a radically different style than what I'm used to.
On top of that, this code probably sucks. I suspect the globalness of the various values is a bad thing. It's also not clear to me if it's appropriate to use references here for the x and y values.
Any hints for improving this code are welcome.
Those ifs in keyPressed can be replaced with a single case. Also, the dosync can be moved outside to wrap the case. In fact, alter can be moved out too, so that if you e.g. decide to change it to commute, there's just the one place to make the change. The result:
(def panel
(proxy [JPanel KeyListener] []
(getPreferredSize [] (Dimension. 100 100))
(keyPressed [e]
(let [keyCode (.getKeyCode e)]
(dosync
(apply alter
(case keyCode
37 [x dec]
38 [y dec]
39 [x inc]
40 [y inc])))
(println keyCode)))
(keyReleased [e])
(keyTyped [e])))
You could also rewrite the imports more concisely:
(import [java.awt Color Dimension event.ActionListener])
(import [javax.swing JFrame JPanel])
-- whether you'd want to is a matter of style.
I would rename drawRectangle to draw-rectangle (that's the idiomatic style for function names in Clojure) and, more to the point, rewrite it as a pure function accepting the coordinates as explicit arguments. Then you could write a small wrapper around that to use your Refs, if indeed your design would benefit from the use of Refs. (Hard to say without knowing how you might want to use & modify them etc.)
Prefer while to (loop [] ... (recur)) (see (doc while) and (clojure.contrib.repl-utils/source while)).
Also -- and this is important -- don't put anything except definitions at top level. That's because top-level forms are actually executed when the code is compiled (try loading a library with a (println :foo) at top level). That infinite loop should be wrapped inside a function; the standard name for a "main" function in Clojure is -main; same goes for panel and frame. This doesn't apply when playing around at the REPL, of course, but it's an important gotcha to know about.
Incidentally, (doto foo ...) returns foo, so you can just write (doto (proxy ...) (.setFocusable true) ...).
Otherwise, I'd say the code is fine. Normally you'd want to put it in a namespace; then all the imports would go in the ns form.
HTH
Edit, while writing the below post in a hurry I forgot to say you must not put parens around constants eg java.awt.Color/WHITE.
In addition to MichaĆ's comments:
when you use an infinite loop, condition it with an atom (eg #switch) thus you'll be able to stop it (unless the loops runs on the repl thread -- so lauch it in a separat thread with "future")
you use refs, so it means the values of x and y are intended to be coordinated (in your code they are not: each update is independent); hence in drawRectangle you should wrap the reads in a dosync to be sure that you get a consistent view -- again in your actual code it doesn't matter because x and y are always updated independently.
hth,
(shameless plug: http://conj-labs.eu/)
Although not exactly Clojure advice - consider using KeyAdapter instead of KeyListener. That way you won't have to provide empty implementations for keyReleased and keyTyped. It's generally a good rule to use adapter classes when you're not needing all of the methods of a Listener interface.