I'm looking through some oo clojure code to better understand it's intricacies.
A lot of the time, when there's a hash map and someone wants to take a key, they write
(-> % :hash :key) , where I think they could just write (:key :hash).
I guess my question is, what is (-> % :hash :key) doing?
-> is the Thread-first macro.
If you have a hash-map like (def mymap {:foo {:bar "s_bar"}}) then the macro will transform
(-> mymap :foo :bar)
into
(:bar (:foo mymap))
and the result will be "s_bar".
Here the macro is used to access values of a nested hash-map and it's similiar to (get-in mymap [:foo :bar])
Related
To build up a data structure I find myself doing a lot of things like:
(let [foo (atom [])]
(do
(swap! foo conj {:foo "bar"})
(swap! foo conj {:foo "baz"}))
#foo)
=> [{:foo "bar"} {:foo "baz"}]
Is this an anti-pattern? I'm using a lot of atoms.
No need for an atom here. You can use immutable data structures:
(-> []
(conj {:foo "bar"})
(conj {:foo "baz"}))
;;=> [{:foo "bar"} {:foo "baz"}]
For folks coming from OOP or imperative languages, this is probably the hardest shift: avoiding mutability.
First off, you don't need the do since it is implied inside let. Then, for this example, plain old -> works great (using my favorite template project):
(ns tst.demo.core
(:use tupelo.core tupelo.test))
(defn stuff
[]
(-> []
(conj {:foo "bar"})
(conj {:foo "baz"})))
(dotest
(is= (stuff)
[{:foo "bar"}
{:foo "baz"}])
Another option is to user reduce:
(defn save-odds
[accum arg]
(if (odd? arg)
(conj accum arg)
accum))
<snip>
(is= (reduce save-odds
[]
(range 6))
[1 3 5]))
Having said that, there is nothing wrong IMHO with using an atom as an accumulator. It is simple & straightforward. And, if the "nasty" mutation of the atom never leaks outside of your function, it cannot cause any complexity in the rest of the program.
"If mutation occurs and no outside function is affected, does
it really matter?"
After all, reduce and friends also use mutation internally, and they are excellent examples of "functional" programming. That is, they are pure functions (have referential transparency), and cause no side effects.
I have (for instance) a mix of data structures such as {:name "Peter" :children "Mark"} and {:name "Mark" :children ["Julia" "John"] i.e. :children value is either a single string or a collection of strings. Other functions in my code expect that the value of :children is always a collection of strings, so I need to adapt the data for them.
Of course I can use something like:
(defn data-adapter [m]
(let [children (:children m)]
(assoc m :children
(if (coll? children)
children
[children]))))
But is there a more idiomatic/laconic way?
I think you will have to take no for an answer.
(if (coll? x) x [x]) is about as terse and expressive as it gets. It’s what people usually use for this problem (sometimes with sequential? instead of coll?).
cond-> enthusiasts like me sometimes try to use it in place of a simple conditional, but here it is no improvement:
(cond-> x (not (coll? x)) vector)
In the context of your code, however, you can do a little better. A lookup and association is best expressed with update:
(defn data-adapter [m]
(update m :children #(if (coll? %) % [%])))
the only advice would be to abstract that logic to some function, to keep your actual business logic clean.
(defn data-adapter [m]
(let [children (:children m)]
(assoc m :children (ensure-coll children))))
or, more concise, with update:
(defn data-adapter [m]
(update m :children ensure-coll))
where ensure-coll could be something like this:
(defn iffun [check & {:keys [t f] :or {t identity f identity}}]
#((if (check %) t f) %))
(def ensure-coll (iffun coll? :f list))
(or whatever another implementation you like)
user> (data-adapter {:children 1})
;;=> {:children (1)}
user> (data-adapter {:children [1]})
;;=> {:children [1]}
Perhaps not idiomatic, but laconic:
(flatten [x])
https://clojuredocs.org/clojure.core/flatten
I'm trying to handle following DSL:
(simple-query
(is :category "car/audi/80")
(is :price 15000))
that went quite smooth, so I added one more thing - options passed to the query:
(simple-query {:page 1 :limit 100}
(is :category "car/audi/80")
(is :price 15000))
and now I have a problem how to handle this case in most civilized way. as you can see simple-query may get hash-map as a first element (followed by long list of criteria) or may have no hash-mapped options at all. moreover, I would like to have defaults as a default set of options in case when some (or all) of them are not provided explicite in query.
this is what I figured out:
(def ^{:dynamic true} *defaults* {:page 1
:limit 50})
(defn simple-query [& body]
(let [opts (first body)
[params criteria] (if (map? opts)
[(merge *defaults* opts) (rest body)]
[*defaults* body])]
(execute-query params criteria)))
I feel it's kind of messy. any idea how to simplify this construction?
To solve this problem in my own code, I have a handy function I'd like you to meet... take-when.
user> (defn take-when [pred [x & more :as fail]]
(if (pred x) [x more] [nil fail]))
#'user/take-when
user> (take-when map? [{:foo :bar} 1 2 3])
[{:foo :bar} (1 2 3)]
user> (take-when map? [1 2 3])
[nil [1 2 3]]
So we can use this to implement a parser for your optional map first argument...
user> (defn maybe-first-map [& args]
(let [defaults {:foo :bar}
[maybe-map args] (take-when map? args)
options (merge defaults maybe-map)]
... ;; do work
))
So as far as I'm concerned, your proposed solution is more or less spot on, I would just clean it up by factoring out parser for grabbing the options map (here into my take-when helper) and by factoring out the merging of defaults into its own binding statement.
As a general matter, using a dynamic var for storing configurations is an antipattern due to potential missbehavior when evaluated lazily.
What about something like this?
(defn simple-query
[& body]
(if (map? (first body))
(execute-query (merge *defaults* (first body)) (rest body))
(execute-query *defaults* body)))
Here is the sample code I want to get to work:
(letfn [(CONC [f] f)
(CONT [f] (str "\newline" f))]
((voodoo "CONC") "hamster"))
Is there some voodo that will make it call the CONC function with hamster as the parameter? That is, is there some way to convert the string "CONC" into a function that is not bound to a namespace but rather to a local binding?
EDIT:
To be clearer, the way this will be called is:
(map #((voodoo (:tag %)) (:value %))
[
{:tag "CONC" :value "hamster"}
{:tag "CONT" :value "gerbil"}
]
)
I'd probably solve this by creating a map of functions indexed by strings:
(def voodoo
{"CONC" (fn [f] f)
"CONT" (fn [f] (str "\newline" f))})
Then your desired code should work directly (exploiting the fact that a map is a function that looks up it's argument)
(map #((voodoo (:tag %)) (:value %))
[
{:tag "CONC" :value "hamster"}
{:tag "CONT" :value "gerbil"}
]
)
Note that the functions here are fully anonymous - you don't need them to be referenced anywhere in the namespace for this to work. In my view this is a good thing, because unless you also need the functions somewhere else then it's best to avoid polluting your top-level namespace too much.
No. Eval does not have access to the local/lexical environment, ever.
Edit: This is not a very good answer, and not really accurate either. You could write voodoo as a macro, and then it doesn't need runtime access to the lexical environment, just compile-time. However, this means it would only work if you know at compile time that the function you want to call is x, and so it wouldn't be very useful - why not just type x instead of (voodoo "x")?
(defmacro voodoo [fname]
(symbol fname))
(letfn [(x [y] (inc y))]
((voodoo "x") 2))
;; 3
(letfn [(x [y] (inc y))]
(let [f "x"]
((voodoo f) 2)))
;; error
Well, it's sort of possible:
(defmacro voodoo [s]
(let [env (zipmap (map (partial list 'quote) (keys &env))
(keys &env))]
`(if-let [v# (~env (symbol ~s))]
v#
(throw (RuntimeException. "no such local")))))
...and now we can do weird stuff like this:
user> (defn example [s]
(letfn [(foo [x] {:foo x})
(bar [x] {:bar x})]
((voodoo s) :quux)))
#'user/example
user> (example "foo")
{:foo :quux}
user> (example "bar")
{:bar :quux}
user> (example "quux")
; Evaluation aborted.
user> *e
#<RuntimeException java.lang.RuntimeException: no such local>
That "Evaluation aborted" means an exception was thrown.
You could also replace the throw branch of the if in voodoo with (resolve (symbol ~s)) to defer to the globals if no local is found:
(defmacro voodoo [s]
(let [env (zipmap (map (partial list 'quote) (keys &env))
(keys &env))]
`(if-let [v# (~env (symbol ~s))]
v#
(resolve (symbol ~s)))))
...and now this works with definition of example as above (though note that if you are experimenting at the REPL, you will need to recompile example after redefining voodoo):
user> (defn quux [x] {:quux x})
#'user/quux
user> (example "quux")
{:quux :quux}
Now, this is an abuse of Clojure's facilities which one would do well to try to do without. If one cannot, one should probably turn to evalive by Michael Fogus; it's a library which provides an "eval-with-locals" facility in the form of an evil function and a couple of utilities. The functionality seems to be well factored too, e.g. something like the ~(zipmap ...) thing above is encapsulated as a macro and evil there appears to be almost a drop-in replacement for eval (add the env parameter and you're good to go). I haven't read the source properly, but I probably will now, looks like fun. :-)
Im not really clear what you are asking for so i'll try a couple answers:
if you have a string that is the name of the function you wish to call:
(def name "+")
((find-var (symbol (str *ns* "/" name))) 1 2 3)
this would give voodoo a deffinition like this:
(defn voodoo [name args] (apply (find-var (symbol (str *ns* "/" name))) args))
#'clojure.core/voodoo
clojure.core=> (voodoo "+" [1 2 3])
6
clojure.core=>
this assumes your function is in the current namepace ns.
if you want to turn a string into a function you could use this pattern
(let [f (eval (read-string "(fn [] 4)"))] (f))
It seems to be a powerful macro, yet I'm failing to apply it to anything but silly examples. Can you show me some real use of it?
Thanks!
Compare:
user> (:baz (:bar (:foo {:foo {:bar {:baz 123}}})))
123
user> (java.io.BufferedReader. (java.io.FileReader. "foo.txt"))
#<BufferedReader java.io.BufferedReader#6e1f8f>
user> (vec (reverse (.split (.replaceAll (.toLowerCase "FOO,BAR,BAZ") "b" "x") ",")))
["xaz" "xar" "foo"]
to:
user> (-> {:foo {:bar {:baz 123}}} :foo :bar :baz)
123
user> (-> "foo.txt" java.io.FileReader. java.io.BufferedReader.)
#<BufferedReader java.io.BufferedReader#7a6c34>
user> (-> "FOO,BAR,BAZ" .toLowerCase (.replaceAll "b" "x") (.split ",") reverse vec)
["xaz" "xar" "foo"]
-> is used when you want a concise way to nest calls. It lets you list the calls in the order they'll be called rather than inside-out, which can be more readable. In the third example, notice how much distance is between some of the arguments and the function they belong to; -> lets you group arguments and function calls a bit more cleanly. Because it's a macro it also works for Java calls, which is nice.
-> isn't that powerful, it just saves you a few parens now and then. Using it or not is a question of style and readability.
Look at the bottom of clojure.zip for extreme examples of how this is helpful.
(-> dz next next next next next next next next next remove up (append-child 'e) root)
Taken from the wiki I've always found this example impressive:
user=> (import '(java.net URL) '(java.util.zip ZipInputStream))
user=> (-> "http://clojure.googlecode.com/files/clojure_20081217.zip"
URL. .openStream ZipInputStream. .getNextEntry bean :name)
As Brian said - it isn't 'useful' so much as 'different style'. I find for all java interop this form of 'start with X' then do Y and Z ... more readable than do Z to Y of X.
Basically you have 4 options:
; imperative style named steps:
(let [X something
b (Y X)
c (Z b)] c)
; nested calls
(Z (Y X))
; threaded calls
(-> X Y Z)
; functional composition
((comp Z Y) X)
I find -> really shines for java interop but avoid it elsewhere.
(defn search-tickets-for [term]
(-> term search zip-soup first :content
((partial filter #(= :body (:tag %)))) first :content
((partial filter #(= :div (:tag %))))
((partial filter #(= "content" ((comp :id :attrs) %))))
((partial map :content)) first ((partial map :content))
((partial map first)) ((partial filter #(= :ul (:tag %)))) first :content
((partial map :content))
((partial map first))
((partial mapcat :content))
((partial filter #(= :h4 (:tag %))))
((partial mapcat :content))
((partial filter #(= :a (:tag %))))
((partial mapcat :content))))
clojurebot from #clojure uses this to search assembla tickets