Passing destructured args through macros - clojure

I'm having some trouble writing macros which use destrucutred arguments. Here is an example:
(defmacro defny
[n args & forms]
`(defn ~n ~args ~#forms))
(defmacro defnz
[n f args & forms]
`(defn ~n ~args
(do
(~f ~#args)
~#forms)))
(defny y
[{:keys [value] :as args}]
(println "Y ARGS" args)
(println "Y VALUE" value))
(defnz z y
[{:keys [value] :as args}]
(println "Z ARGS" args)
(println "Z VALUE" value))
Here, I have two macros, defny which simply calls through to defn, and defnz, which does the same, but additionally accepts another function which invokes prior to the function body with defnz's args.
When I invoke z, I expect to see both values and args printed out the same, but instead I get:
(z {:value 1})
Y ARGS {:keys [1], :as {:value 1}}
Y VALUE nil
Z ARGS {:value 1}
Z VALUE 1
=> nil
I can see why this is happening, the destructured args {:keys [1] :as {:value 1}} are getting passed to y, but I'm not sure how to fix the macro defnz so that the destructured args can be passed in properly.

you can easily see the mistake with macroexpansion:
(defnz z y
[{:keys [value] :as args}]
(println "Z ARGS" args)
(println "Z VALUE" value))
expands to:
(defn z [{:keys [value], :as args}]
(do
(y [{:keys [value], :as args}])
(println "Z ARGS" args)
(println "Z VALUE" value)))
you are correct: you pass the whole args form to y, but what you need is make that line to be expanded to (y args) where args is just a plain symbol. To do it within macro (which turns symbols no namespace qualified ones) you should use "quote-unquote" trick:
(defmacro defnz
[n f args & forms]
`(defn ~n ~args
(do
(~f ~'args)
~#forms)))
now the expansion would be correct:
(defn z [{:keys [value], :as args}]
(do (y args) (println "Z ARGS" args) (println "Z VALUE" value)))
but it is rather bad idea, since you don't know exactly what allargs name would be passed to defnz, like {:keys [value] :as all-of-them} would fail (because defnz expects it to be args. You can fix it by dynamically retrieving allargs name in defnz:
(defmacro defnz
[n f [a :as args] & forms]
(let [a (if (:as a) a (assoc a :as 'everything))]
`(defn ~n ~[a]
(do
(~f ~(:as a))
~#forms))))
so now it would expand to the following:
(defnz z y
[{:keys [value] :as args}]
(println "Z ARGS" args)
(println "Z VALUE" value))
;;(defn z [{:keys [value], :as args}]
;; (do (y args) (println "Z ARGS" args) (println "Z VALUE" value)))
(defnz z y
[{:keys [value] :as all-args}]
(println "Z ARGS" all-args)
(println "Z VALUE" value))
;;(defn z [{:keys [value], :as all-args}]
;; (do
;; (y all-args)
;; (println "Z ARGS" all-args)
;; (println "Z VALUE" value)))
(defnz z y
[{:keys [value]}]
(println "Z ARGS" everything)
(println "Z VALUE" value))
;;(defn z [{:keys [value], :as everything}]
;; (do
;; (y everything)
;; (println "Z ARGS" everything)
;; (println "Z VALUE" value)))

Related

Function for applying to a user with a various number of data fields

The question was born when I was practicing an Observer topic in a tutorial
I am trying to apply the function to the user but cannot use user's data fields like name, surname.
Let's say that the user may have various number of data fields so we must use & args argument. My code that does not work:
(ns observer.core)
(def user {:name "Alan" :surname "Smith" :alias "Mike"})
(def user2 {:name "Jane" :surname "Smith"})
(apply
(fn [& args] (println (str "I am " (:name args) " " (:surname args) ".")))
user)
(apply
(fn [& args] (println (str "My sister is " (:name args) " " (:surname args) ".")))
user2)
The output:
I am .
My sister is .
observer.core>
How to fix it regarding that the apply function must be used?
apply converts a map to a seq, i.e.
{:name "Alan" :surname "Smith" :alias "Mike"} becomes ([:name "Alan"] [:surname "Smith"] [:alias "Mike"])
You could put it back into a map, if that is what you need:
(let [user {:name "Alan" :surname "Smith" :alias "Mike"}]
(apply
(fn [& args]
(let [args (into {} args)]
(println (str "I am " (:name args) " " (:surname args) "."))))
user))
but this looks a bit of a stretch to me. I believe the solution could have been better if I knew how this function is supposed to be used.
Usually there are two types of functions: (fn :text "some" :row 25) and (fn {:text "some" :row 25}).
In the spirit of learning:
Check out Clojure - Cheatsheet.
10 years with Clojure, and I still use it daily.
(apply some-func (list x y z)) becomes (some-func x y z), because apply assumes that the second argument is a list (which it then unpacks).
And what you are currently doing is collecting all the arguments back into a list called args
(def user {:name "Alan" :surname "Smith" :alias "Mike"})
(apply
(fn [& args]
(prn 'ARGS args) ;; lets see what is actually in args
(println (str "I am " (:name args) " " (:surname args) ".")))
user)
;; -> ARGS ([:name "Alan"] [:surname "Smith"] [:alias "Mike"])
;; -> I am .
And the outut is as #akond says.
You could, of course, put 'user' in a vector (or list), but then don't use '&' to collect everything back into a list, (which you would then have to pick stuff out of again):
(def user {:name "Alan" :surname "Smith" :alias "Mike"})
(apply
(fn [args]
(prn 'ARGS args)
(println (str "I am " (:name args) " " (:surname args) ".")))
[user])
That would give you the output you expected. But this is a bit strange, perhaps, but certainly viable if you must use apply and you can control the "list" part of the argument.
So, #akond's solution is simple and clean.
And augmenting it with Clojure "destructing":
(def user {:name "Alan" :surname "Smith" :alias "Mike"})
(apply
(fn [& args]
(let [{:keys [name surname alias]} (into {} args)]
(println (str "I am " name " " surname "." (when alias (str " But call me " alias "!"))))))
user)
I believe you intended to do something like this:
(def user {:name "Alan" :surname "Smith" :alias "Mike"})
(def user2 {:name "Jane" :surname "Smith"})
(defn fn-1
[item]
(println (str "I am " (:name item) " " (:surname item) ".")) )
(defn fn-2
[item]
(println (str "My sister is " (:name item) " " (:surname item) ".")))
(fn-1 user)
(fn-2 user2)
with result:
I am Alan Smith.
My sister is Jane Smith.
One has to wrap a user object or the map by a list.
(ns observer.core)
(defrecord Person [name balance])
(def user (Person. "Alan" 150.34))
(def user2 {:name "Adam" :balance 629.74})
(def observers (atom #{}))
(swap! observers conj (fn [l] (println (str "2. " (:name l)))))
(swap! observers conj (fn [l] (println (str "1. " (:balance l)))))
(println "user")
(vec (map #(apply % (list user)) #observers))
(println "\nuser2")
(vec (map #(apply % (list user2)) #observers))
Output
user
1. 150.34
2. Alan
user2
1. 629.74
2. Adam
observer.core>

Clojure multi method dispatch on class of all arguments

I am trying to write a multi-method which dispatches based on the types of all the arguments passed to it, but I am struggling to figure out how to write such a distpatch fn.
By that I mean, given:
(defmulti foo (fn [& args] ...))
(defmethod foo String
[& args]
(println "All strings"))
(defmethod foo Long
[& args]
(println "All longs"))
(defmethod foo Number
[& args]
(println "All numbers"))
(defmethod foo :default
[& args]
(println "Default"))
Then we would get:
(foo "foo" "bar" "baz") => "All strings"
(foo 10 20 30) => "All longs"
(foo 0.5 10 2/3) => "All numbers"
(foo "foo" 10 #{:a 1 :b 2}) => "Default"
(defmulti foo (fn [& args] (into #{} (map class args))))
(defmethod foo #{String}
[& args]
(println "All strings"))
(defmethod foo #{Long}
[& args]
(println "All longs"))
(defmethod foo #{Number}
[& args]
(println "All numbers"))
(defmethod foo :default
[& args]
(println "Default"))
Results in:
(foo "foo" "bar" "baz") => "All strings"
(foo 10 20 30) => "All longs"
(foo 0.5 10 2/3) => "Default"
(foo "foo" 10 #{:a 1 :b 2}) => "Default"
So the Number example doesn't work.
Here is the dispatcher fn:
(defmulti foo (fn [& args]
(let [m (distinct (map type args))]
(when (= 1 (count m))
(first m)))))

Is there a short form for creating hash-map in Clojure?

Is there a short form/macro that allows me to do
(defn f [a b c]
{a b c})
instead of
(defn f [a b c]
{:a a :b b :c c})
(defmacro as-map [& syms]
(zipmap (map keyword syms) syms))
Usage:
(def a 42)
(def b :foo)
(as-map a b)
;;-> {:a 42 :b :foo}
Note that to support namespaced keywords, you'd have to drop support for ns aliases if you want to keep it as short:
(defmacro as-map [& syms]
(zipmap (map keyword syms) (map (comp symbol name) syms)))
Usage:
(def a 42)
(def b :foo)
(as-map example/a foo-of/b)
;;-> {:example/a 42 :foo-of/b :foo}
Advice: Likely not a good idea, saves you a few keyboard hits at the cost of readability and expressivity and flexibility in naming local bindings.
This shows the steps. Remove the println's for actual use:
(ns clj.core
(:gen-class))
(defmacro hasher [& args]
(let [keywords (map keyword args)
values args
keyvals-list (interleave keywords values)
]
(println "keywords " keywords)
(println "values " values)
(println "keyvals-list " keyvals-list)
`(hash-map ~#keyvals-list)
)
)
(def a 1)
(def b 2)
(println \newline "result: " (hasher a b))
> lein run
keywords (:a :b)
values (a b)
keyvals-list (:a a :b b)
result: {:b 2, :a 1}
This is an old snippet of mine I've had kicking around for a while.
(declare ^:private restructure*)
(defn ^:private restructure-1 [m [e k]]
(cond
(= :strs e) (reduce #(assoc %1 (name %2) %2) m k)
(= :keys e) (reduce #(assoc %1 (keyword (namespace %2) (name %2)) %2) m k)
:else (assoc m k (restructure* e))))
(defn ^:private restructure* [form]
(if-not (map? form)
form
(as-> {} v
(reduce restructure-1 v form)
`(hash-map ~#(mapcat identity v)))))
(defmacro restructure [form]
(restructure* form))
The idea is that it provides the complement of clojure.core/destructure which goes from a destructuring form to bindings, this captures bindings and constructs a datastructure.
(let [x 1 y 2 z 3]
(restructure {:keys [x y z]}))
;; => {:x 1 :y 2 :z 3}

clojure.lang.Symbol cannot be cast to java.lang.CharSequence in macro

I'm writing a macro to allow pass the clauses as a parameter to functions:
(defmacro parse-cmd [command & body]
(let [parts (str/split command #" ")
cmd (first parts)
args (into [] (rest parts))
clauses (partition 2 2 body)]
`(case ~cmd
~#(mapcat (fn [c] [(nth c 0) `(apply ~(nth c 1) ~args)]) clauses))))
(defn mysum [a b]
(+ (Integer. a) (Integer. b)))
(parse-cmd "SET 1 1" "SET" "GET" println)
2
This works well when cmd is a string, however with a var:
(def cmd "SET 1 1")
(parse-cmd cmd "SET" "GET" println)
I get ClassCastException clojure.lang.Symbol cannot be cast to java.lang.CharSequenceq clojure.string/split (string.clj:222)
I guess I should prevent the evaluation of the let too, but I can't make it work:
(defmacro parse-cmd [command & body]
`(let [parts# (str/split ~command #" ")
cmd# (first parts#)
args# (into [] (rest parts#))
clauses# (partition 2 2 ~body)]
(case cmd#
(mapcat (fn [c#] [(nth c# 0) `(apply ~(nth c# 1) args#)]) clauses#))))
With this definition I get:
ClassCastException java.lang.String cannot be cast to clojure.lang.IFn kvstore.replication/eval12098 (form-init7453673077215360561.clj:1)
let's macroexpand this (for your second macro)
(parse-cmd "SET 1 1" "SET" mysum "GET" println)
it expands to:
(let [parts__31433__auto__ (str/split "SET 1 1" #" ")
cmd__31434__auto__ (first parts__31433__auto__)
args__31435__auto__ (into [] (rest parts__31433__auto__))
clauses__31436__auto__ (partition
2
2
("SET" mysum "GET" println))]
(case
cmd__31434__auto__
(mapcat
(fn [c__31437__auto__] [(nth c__31437__auto__ 0)
(seq
(concat
(list 'apply)
(list (nth c__31437__auto__ 1))
(list 'args__31432__auto__)))])
clauses__31436__auto__)))
there are two problems here:
1) you generate this code: ("SET" mysum "GET" println), which obviously causes your exception, because "SET" is not a function
2) you generate the wrong case expression, I see that you have forgotten to unquote-splice your mapcat
Let's try to fix this:
first of all unquote mapcat; then you can move clauses out of your generated let, because it can be totally done at compile-time:
(defmacro parse-cmd [command & body]
(let [clauses (partition 2 2 body)]
`(let [parts# (str/split ~command #" ")
cmd# (first parts#)
args# (into [] (rest parts#))]
(case cmd#
~#(mapcat (fn [c] [(nth c 0) `(apply ~(nth c 1) args#)]) clauses)))))
now let's check the expansion:
(let [parts__31653__auto__ (str/split "SET 1 1" #" ")
cmd__31654__auto__ (first parts__31653__auto__)
args__31655__auto__ (into [] (rest parts__31653__auto__))]
(case
cmd__31654__auto__
"SET"
(apply mysum args__31652__auto__)
"GET"
(apply println args__31652__auto__)))
ok. looks better. let's try to run it:
(parse-cmd "SET 1 1" "SET" mysum "GET" println)
we have another error now:
CompilerException java.lang.RuntimeException: Unable to resolve symbol: args__31652__auto__ in this context, compiling:(*cider-repl ttask*:2893:12)
so expansion also shows us this:
args__31655__auto__ (into [] (rest parts__31653__auto__))
...
(apply mysum args__31652__auto__)
so there are different symbols for args# here. That's because the scope of the generated symbol name is one syntax-quote. So inner syntax-quote with apply generates the new one. You should use gensym to fix that:
(defmacro parse-cmd [command & body]
(let [clauses (partition 2 2 body)
args-sym (gensym "args")]
`(let [parts# (str/split ~command #" ")
cmd# (first parts#)
~args-sym (into [] (rest parts#))]
(case cmd#
~#(mapcat (fn [c] [(nth c 0) `(apply ~(nth c 1) ~args-sym)]) clauses)))))
ok now it should work properly:
ttask.core> (parse-cmd "SET 1 1" "SET" mysum "GET" println)
2
ttask.core> (parse-cmd cmd "SET" mysum "GET" println)
2
great!
I would also recommend you to use destructuring in a mapcat function and quoted let, to make it more readable:
(defmacro parse-cmd [command & body]
(let [clauses (partition 2 2 body)
args-sym (gensym "args")]
`(let [[cmd# & ~args-sym] (str/split ~command #" ")]
(case cmd#
~#(mapcat (fn [[op fun]] [op `(apply ~fun ~args-sym)]) clauses)))))
But if it's not just an exercise in writing macros, you shouldn't use the macro for that, since you pass just string and function references here, so anyway you shall evaluate everything in runtime.
(defn parse-cmd-1 [command & body]
(let [[cmd & args] (str/split command #" ")
commands-map (apply hash-map body)]
(apply (commands-map cmd) args)))

Setting a debug function from the command line in Clojure

I have a namespace like this:
(ns foo.core)
(def ^:dynamic *debug-fn*
"A function taking arguments [bar baz]"
nil)
(defn bar-info
[bar _]
(println bar))
(defn baz-info
[_ baz]
(println baz))
(defn do-stuff
[bar baz]
(when *debug-fn* (*debug-fn* bar baz)))
(defn -main
[& {:keys [debug-fn]}]
(binding [*debug-fn* (symbol debug-fn)] ;; THIS WON'T WORK!
(do-stuff 27 42)))
What I would like to do is allow a debug function to be specified from the command line like this: lein run bar-info or lein run baz-info.
I'm not sure how to take the string specified as a command-line argument and turn it into the namespace-qualified function to bind. Do I need a macro to do this?
Use ns-resolve, you will need to specify namespace where your function is defined though.
user=> (defn f [n] (* n n n))
#'user/f
user=> ((ns-resolve *ns* (symbol "f")) 10)
1000
Use alter-var-root:
user=> (doc alter-var-root)
-------------------------
clojure.core/alter-var-root
([v f & args])
Atomically alters the root binding of var v by applying f to its
current value plus any args
nil
user=> (alter-var-root #'*debug-fn* (fn [v] (fn [x] (println x) x)))
#<user$eval171$fn__172$fn__173 user$eval171$fn__172$fn__173#7c93d88e>
user=> (*debug-fn* 1)
1
1
Though I've accepted Guillermo's answer above, I figured that it might also be useful to add the solution I ended up going with:
(def debug-fns
{:bar-info (fn [bar _] (println bar))
:baz-info (fn [_ baz] (println baz))
(def active-debug-fns (atom []))
(defn activate-debug-fn!
[fn-key]
(let [f (debug-fns fn-key)]
(if f
(swap! active-debug-fns conj f)
(warn (str "Debug function " fn-key " not found! Available functions are: "
(join " " (map name (keys debug-fns))))))))
(defn debug-fn-keys
[args]
(if (args "--debug")
(split (or (args "--debug") "") #",")
[]))
(defn do-stuff
[bar baz]
(doseq [f #active-debug-fns]
(f bar baz)))
(defn -main
[& args]
(let [args (apply hash-map args)]
(doseq [f (debug-fn-keys args)]
(activate-debug-fn! (keyword k)))
(do-stuff 27 42)))
So now you can say something like lein run --debug bar-info to get info on bars, or lein run --debug bar,baz to get info on both bars and bazes.
Any suggestions to make this more idiomatic will be happily accepted and edited in. :)