I came across a ?> statement within a -> block in some code I was trying to understand. I searched clojuredocs, but came up blank. What does this do?
Code I was trying to understand:
(-> (apply time/t -2 date-fields)
(?> (:grain token-fields) (assoc :grain (:grain token-fields)))
(?> (:timezone token-fields) (assoc :timezone (:timezone token-fields))))
I found it. It comes from the Plumbing library.
To quote the docs:
?> macro
(?> arg do-it? & rest)
Conditional single-arrow operation (-> m (?> add-kv? (assoc :k :v)))
Here's the actual source code of the macro:
(defmacro ?>
"Conditional single-arrow operation (-> m (?> add-kv? (assoc :k :v)))"
[arg do-it? & rest]
`(if ~do-it?
(-> ~arg ~#rest)
~arg))
Related
I have this ns with a macro in it. The annoying thing im dealing with is that the taoensso.timbre macro only works as a variadic expression (timbre/info "a" "b" "c"). A list of items wont log right (timbre/info ["a" "b" "c"]). Im trying to create a wrapper macro that lets the code call (logger/info) in the same variadic form, then process all elements, and then pass to timbre/info
(ns logger
(:require [taoensso.timbre :as timbre :include-macros true])) ; a third party logger
;; A bit of pseudo code here. If you pass in a vector of args, you should get a vector of args with some changes
(defn scrub [args]
(if (listy)
(mapv (fn [a] (scrub args) args)
(if (is-entry a) {:a "xxx"} a)
(defmacro info
[& args]
`(timbre/info ~#(scrub args)))
This doesnt work because scrub is called immediately and wont resolve symbols passed in. I need something like either of these that dont work.
(defmacro info
[& args]
`(timbre/info #(scrub-log-pii ~args)))
(defmacro info
[& args]
`(timbre/info ~#('scrub-log-pii args)))
My last thought was to try to wrap the timbre macro in a function so the macro and evaluation happen in the right order. There is however, no way to "apply" to a macro.
(defn info3
[& args]
(timbre/info (scrub-log-pii (vec args))))
Any ideas?
not exactly an answer to the question as phrased (macro application stuff), but rather the practical timbre solution, that may be applicable in your specific case:
here you can see that all timbre macros use log! macro, which in turn accepts the collection of args.
so, just implementing your procedure as
(defmacro info* [args] `(log! :info :p ~args ~{:?line (fline &form)}))
should do the trick.
You have encountered a problem of using macros known as "turtles all the way down". That is, instead of using function composition, you may need to write a wrapper macro, then another wrapper macro for that, etc.
The detailed steps to writing a macro are described in this answer:
How do I write a Clojure threading macro?
For your specific problem, we could to this:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test)
(:require
[clojure.pprint :as pp]))
(defn infix-impl
[a op b]
(list op a b))
(defmacro infix
"Allows user to have Java-style infix operators:
(infix 2 + 3)
"
[a op b] (infix-impl a op b))
(defn infix-seq-impl
[args]
`(let [form# (cons 'infix ~args)
result# (eval form#)]
result#))
(defmacro infix-seq
[args] (infix-seq-impl args))
(dotest
(is= 5 (infix 2 + 3))
(let [params '[2 + 3]]
(pp/pprint (infix-seq-impl 'params))
(is= 5 (infix-seq params))))
Here we use the infix macro to show how to create a wrapper macro infix-seq that accepts a sequence of params instead of N scalar params. The printed output shows the generated code:
(clojure.core/let
[form__24889__auto__ (clojure.core/cons 'tst.demo.core/infix params)
result__24890__auto__ (clojure.core/eval form__24889__auto__)]
result__24890__auto__)
A more general version
The applied macro below allows you to pass in the name of the macro to be "applied" to the param sequence:
(defn applied-impl
[f args]
`(let [form# (cons ~f ~args)
result# (eval form#)]
result#))
(defmacro applied
[f args] (applied-impl f args))
(dotest
(nl)
(let [params '[2 + 3]]
; (applied 'infix params) ; using a single quote fails
(is= 5 (applied `infix params)) ; using a backquote works
(is= 5 (applied 'tst.demo.core/infix params)) ; can also use fully-qualified symbol with single-quote
(is= 5 (applied (quote tst.demo.core/infix) params)) ; single-quote is a "reader macro" for (quote ...)
))
I have the following macro:
(defmacro my-macro [k]
`(do
(def pair
[
k
~(symbol (str "-" (name k)))]
)))
...which expands to:
(macroexpand-1 `(my-macro :n/k))
(do (def user/pair [user/k -k]))
...but instead I would like it to expand to
(do (def user/pair [:n/k -k]))
How can I make the macro keep the keyword and its namespace?
Thanks!
You need to escape k from the syntax quote using ~k:
(defmacro my-macro [k]
`(def ~'pair [~k ~(symbol (str "-" (name k)))]))
I've made a few other changes here as well:
Idiomatic formatting. Don't put ( or [ at the end of a line -- and put closing ) and ] on the same line as the expression they close.
do is entirely superfluous here.
If you want the macro to expand to (def pair ...), then you need to
escape out of the syntax quote (~)
quote the symbol pair (i.e., 'pair)
Putting this together, you have ~'pair. The reason you have to do this is because, in Clojure, `<symbol> is read as (quote <current-namespace>/foo>), where <current-namespace> stands for the current namespace. But def doesn't take names that are namespaced. Hence the ~' dance.
(But you probably want to parameterize on pair anyway ... otherwise, it's not very useful to use my-macro more than once per namespace.)
Overall, this seems like a very odd macro. I don't know what you're trying to accomplish, but I would probably take a different approach.
you can use the namespace and name function to extract the parts you want from the keyword passed in and combine them as required:
user> (defmacro my-macro [k]
`(do
(def pair
[~(keyword (str (namespace k) "/" (name k)))
~(symbol (str "-" (name k)))])))
#'user/my-macro
user> (macroexpand-1 `(my-macro :n/k))
(do (def user/pair [:n/k -k]))
Revised Answer
There are 2 things a bit confusing about your question & I misread it earlier.
You should use a regular single-quote ' with macroexpand-1, not the back-tic `. The back-tick is normally used only in a macro definition to delineate a piece of "template code".
I just noticed that the arg in the macro definition is k, and the keyword you use in the example is :n/k. These duplicate names will cause confusion.
Let's restate the problem:
(ns clj.demo)
(defmacro my-macro [arg]
`(do
(def pair
[
arg
~(symbol (str "-" (name arg)))]
)))
(println (macroexpand-1 `(my-macro :n/k)))
;=> (do (def clj.demo/pair [clj.demo/arg -k]))
So we are in the clj.demo namespace, which gets applied to the symbols pair and arg. We need to substitue the argument arg using ~:
(ns clj.demo)
(defmacro my-macro [arg]
`(do
(def pair
[
~arg
~(symbol (str "-" (name arg)))]
)))
(println (macroexpand-1 '(my-macro :n/k)))
;=> (do (def clj.demo/pair [:n/k -k]))
Which is what you want.
I am programming something that doesn't have side-effects, but my code is not very readable.
Consider the following piece of code:
(let [csv_data (if header_row (cons header_row data_rows) data_rows)]
)
I'm trying to use csv_data in a block of code. What is a clean way of conditioning on the presence of a header_row? I've looked at if-let, but couldn't see how that could help here.
I have run into similar situations with functional for-loops as well where I'm binding the result to a local variable, and the code looks like a pile of expressions.
Do I really have to create a separate helper function in so many cases?
What am I missing here?
Use the cond->> macro
(let [csv_data (cond->> data_rows
header_row (cons header-row)]
)
It works like the regular ->> macro, but before each threading form a test expression has to be placed that determines whether the threading form will be used.
There is also cond->. Read more about threading macros here: Official threading macros guide
First, don't use underscore, prefer dashes.
Second, there is nothing wrong with a little helper function; after all, this seems to be a requirement for handling your particular data format.
Third, if you can change your data so that you can skip those decisions and have a uniform representation for all corner cases, this is even better. A header row contains a different kind of data (column names?), so you might prefer to keep them separate:
(let [csv {:header header :rows rows}]
...)
Or maybe at some point you could have "headers" and "rows" be of the same type: sequences of rows. Then you can concat them directly.
The ensure-x idiom is a very common way to normalize your data:
(defn ensure-list [data]
(and data (list data)))
For example:
user=> (ensure-list "something")
("something")
user=> (ensure-list ())
(())
user=> (ensure-list nil)
nil
And thus:
(let [csv (concat (ensure-list header) rows)]
...)
i would propose an utility macro. Something like this:
(defmacro update-when [check val-to-update f & params]
`(if-let [x# ~check]
(~f x# ~val-to-update ~#params)
~val-to-update))
user> (let [header-row :header
data-rows [:data1 :data2]]
(let [csv-data (update-when header-row data-rows cons)]
csv-data))
;;=> (:header :data1 :data2)
user> (let [header-row nil
data-rows [:data1 :data2]]
(let [csv-data (update-when header-row data-rows cons)]
csv-data))
;;=> [:data1 :data2]
it is quite universal, and lets you fulfill more complex tasks then just simple consing. Like for example you want to reverse some coll if check is trueish, and concat another list...
user> (let [header-row :header
data-rows [:data1 :data2]]
(let [csv-data (update-when header-row data-rows
(fn [h d & params] (apply concat (reverse d) params))
[1 2 3] ['a 'b 'c])]
csv-data))
;;=> (:data2 :data1 1 2 3 a b c)
update
as noticed by #amalloy , this macro should be a function:
(defn update-when [check val-to-update f & params]
(if check
(apply f check val-to-update params)
val-to-update))
After thinking about the "cost" of a one-line helper function in the namespace I've came up with a local function instead:
(let [merge_header_fn (fn [header_row data_rows]
(if header_row
(cons header_row data_rows)
data_rows))
csv_data (merge_header_fn header_row data_rows) ]
...
<use csv_data>
...
)
Unless someone can suggest a more elegant way of handling this, I will keep this as an answer.
I'm trying to import data from StackOverflow to Neo4j using clojure and the neocons library. Excuse me for being a bit of a newbie.
Here's my main function in Leiningen:
(defn -main
[& args]
(let [neo4j-conn (nr/connect "http://localhost:7777/db/data/")]
(cypher/tquery neo4j-conn "MATCH n OPTIONAL MATCH n-[r]-() DELETE n, r")
(for [page (range 1 6)]
(let [data (parse-string (stackoverflow-get-questions page))
questions (data "items")
has-more (data "has_more")
question-ids (map #(%1 "question_id") questions)
answers ((parse-string (stackoverflow-get-answers question-ids)) "items")]
(map #(import-question %1 neo4j-conn) questions)
(map #(import-answer %1 neo4j-conn) answers)
)
)
)
)
I've defined import-question and import-answer functions and those work fine independently. In fact, what's weird is I can remove either one of those import-* lines and the other will work just fine.
Can anybody see if I'm doing something simple that's wrong?
Both map and for are lazy, and will do nothing at all unless you consume their results.
The first map call ends up being a noop because there is no way for anything to consume it's output. Try wrapping the for and at least the first map call in a call to dorun, or doall if you plan on consuming the result.
Also, you can replace for with doseq, which is identical except that it returns nil, eagerly consumes its input, and can contain multiple forms in its body.
Here is what your code could look like using doseq:
(defn -main
[& args]
(let [neo4j-conn (nr/connect "http://localhost:7777/db/data/")]
(cypher/tquery neo4j-conn "MATCH n OPTIONAL MATCH n-[r]-() DELETE n, r")
(doseq [page (range 1 6)
:let [data (parse-string (stackoverflow-get-questions page))
questions (data "items")
has-more (data "has_more")
question-ids (map #(%1 "question_id") questions)
answers ((parse-string (stackoverflow-get-answers question-ids)) "items")]]
(doseq [q questions]
(import-question q neo4j-conn))
(doseq [a answers]
(import-answer a neo4j-conn)))))
I have this function :
(defn list-data [alist filter-text]
(filter (fn [x] (if (nil? filter-text) true
(> (.indexOf x filter-text) -1))) alist))
(list-data ["Lion" "Zebra" "Buffalo" "Antelope"] "a")
;=> ("Zebra" "Buffalo")
Is there a more idiomatic way of writing this function and respect the fact that I don't want a case-sensitive filter, meaning I would like that (list-data ["Lion" "Zebra" "Buffalo" "Antelope"] "a")returns the following:
;=> ("Zebra" "Buffalo" "Antelope")
Thanks!
(This would need to work in a .cljs file)
In Clojure itself you would normally do this with a regular expression. In Java regular expressions you can do this by giving in a flag for case-insensitivity for the match you want to make, or at the start of the regex for global case-insensitivity:
(filter #(re-find #"(?i)a" %)
["Lion" "Zebra" "Buffalo" "Antelope"])
Pure Javascript regular expressions only support global flags. They are given in as string as the second parameter to the regex constructor:
(filter #(re-find (js/RegExp. "a" "i") %)
["Lion" "Zebra" "Buffalo" "Antelope"])
However, as convenience and to keep the regexes between Java and Javascript similar, the Clojurescript reader translates global java style flags (those at the start of the regex) to their Javascript global equivalent.
So the first example works in Clojurescript as well. Be aware though that non-global flags won't work in Clojurescript where they would work in Clojure.
(defn list-data [alist filter-text]
(if-let [filter-text (some-> filter-text not-empty .toLowerCase)]
(filter #(-> %
.toLowerCase
(.indexOf filter-text)
(not= -1))
alist)
alist))