Clojure destructure map using :keys with qualified keywords not working - clojure

I've tried this with both 1.7.0 and 1.8.0 and it appears that Clojure does not destructure maps using :keys whose keys are fully qualified. I don't think it has to do with being in the tail on the arguments as it doesn't work when I switch around the function argument positions as well.
(ns foo.sandbox)
(def foo ::foo)
(def bar ::bar)
(defn normalize-vals
[mmap & [{:keys [foo bar] :as ops}]]
(println "normalize-vals " ops " and foo " foo " bar" bar))
(normalize-vals {} {foo 1 bar 2})
=> normalize-vals {:foo.sandbox/foo 1, :foo.sandbox/bar 2} and foo nil bar nil
However; this works:
(defn normalize-vals
[mmap & [{a foo b bar :as ops}]]
(println "normalize-vals " ops " and foo " a " bar" b))
(normalize-vals {} {foo 1 bar 2})
=> normalize-vals {:cmt.sandbox/foo 1, :cmt.sandbox/bar 2} and foo 1 bar 2
Is this a defect?

Let's take your function as is:
(defn normalize-vals
[mmap & [{:keys [foo bar] :as ops}]]
(println "normalize-vals " ops " and foo " foo " bar" bar))
Note that foo & bar above are local bindings, they do not refer to anything outside of the function.
...and rewrite the other bits of code a bit:
(def foo-const ::foo)
(def bar-const ::bar)
Don't pay too much attention to the naming here, the point is to use different names.
(normalize-vals {} {foo 1 bar 2})
;; error: ...Unable to resolve symbol: foo in this context...
(normalize-vals {} {foo-const 1 bar-const 2})
;; prints: normalize-vals {:user/foo 1, :user/bar 2} and foo nil bar nil
The lesson should be to use unique names as much as possible.
Why did the 2nd variant work?
In the destructuring form {a foo b bar :as ops};
a & b are new local bindings. If we had a var named a or b these would override them within the scope of this function.
foo & bar are resolved from the environment. If we don't suffix them with -const as above we would get a CompilerException just like the one above.

You are destructuring using unqualified keywords, so instead of:
[mmap & [{:keys [foo bar] :as ops}]]
you should use
[mmap & [{:keys [::foo ::bar] :as ops}]]
You can use clojure.walk/macroexpand-all to expand normalize-vals:
(clojure.walk/macroexpand-all '(defn normalize-vals
[mmap & [{:keys [foo bar] :as ops}]]
(println "normalize-vals " ops " and foo " foo " bar" bar)))
=> (def normalize-vals (fn* ([mmap & p__26720] (let* [vec__26721 p__26720 map__26722 (clojure.core/nth vec__26721 0 nil) map__26722 (if (clojure.core/seq? map__26722) (. clojure.lang.PersistentHashMap create (clojure.core/seq map__26722)) map__26722) ops map__26722 foo (clojure.core/get map__26722 :foo) bar (clojure.core/get map__26722 :bar)] (println "normalize-vals " ops " and foo " foo " bar" bar)))))
The important part of the expansion to note is:
foo (clojure.core/get map__26722 :foo)
bar (clojure.core/get map__26722 :bar)
The keys in the map destructuring are therefore converted to keywords at compile time and the values from the foo and bar vars in the namespace will not be used.

Related

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 it possible to destructure in case for use in a threading macro?

(def val { :type "bar" })
(-> val
(case ???
"bar" "bar type"
"baz" "baz type"
"other type"))
I'd like to include a case in a threading macro so I can branch based on one of the keys of val, a hash map. Is this possible?
EDIT: I need to thread val not a key from val as further functions will need the whole of val. I essentially want to branch to a function within the threading macro based on a key of val. But still pass val onward.
Consider using a multimethod dispatch here:
(defmulti some-operation :type)
(defmethod some-operation "bar"
[val]
(println "Bar type!")
(assoc val :x 42))
(defmethod some-operation "baz"
[val]
(println "Baz type!")
(assoc val :x 100))
(-> {:type "bar"}
some-operation
some-other-operation)
This is something which seems to work and serves as a better example of what I want to do:
(def val { :type "bar" })
(-> val
(do-something-to-val-based-on-type)
(do-something-else-to-val))
(defn do-something-to-val-based-on-type [val]
(let [:type (:type val)]
(case type
"bar" (do-something-to-bar-type-val val)
"baz" (do-something-to-baz-type-val val)
val))) ;; default, no-op
(defn do-something-to-bar-type-val [val]
;; something
val)
(defn do-something-to-baz-type-val [val]
;; something
val)
also, since a threading macro simply adds an item to a seq for every "action", you can easily use anonymous function for that:
user> (def val { :type "bar" })
#'user/val
user> (-> val
((fn [{type :type}]
(case type
"bar" "bar type"
"baz" "baz type"
"other type"))))
;;=> "bar type"
if you wish, you can also make up special macro, rearranging let for usage in ->:
user> (defmacro let-inv [x binding & body]
`(let [~binding ~x] ~#body))
#'user/let-inv
user> (-> val
(let-inv {type :type}
(case type
"bar" "bar type"
"baz" "baz type"
"other type")))
;;=> "bar type"
This is easily accomplished using the it-> threading macro from the Tupelo library:
(ns tst.clj.core
(:use clj.core tupelo.test)
(:require [tupelo.core :as t] ))
(t/refer-tupelo)
(def val { :type "bar" })
(println "result => "
(it-> val
(case (grab :type it)
"bar" "bar-type"
"baz" "baz-type"
"other-type")))
to yield the desired result:
result => bar-type
The it-> macro assigns the intermediate value to the symbol it at each stage of the pipeline. In this case we use the grab function to extract the :type value. It works like (:type it) but will throw if the key is not found.
Another example:
(it-> 1
(inc it) ; thread-first or thread-last
(+ it 3) ; thread-first
(/ 10 it) ; thread-last
(str "We need to order " it " items." ) ; middle of 3 arguments
;=> "We need to order 2 items." )

How to code a variadic defmulti/defmethod in clojure

I have a defmulti/defmethod group that take pairs of arguments like so...
(defmulti foo "some explanation" (fn [arg1 arg2] (mapv class [arg1 arg2])))
(defmethod foo [N P] (->L 1 2 3))
(defmethod foo [L P] (->N 5))
(defmethod foo [P N] (->L 6 7 8))
...
Which are called in the way you might expect.
(foo (->N 9) (->P 9))
What I would like is to call 'foo' with more than 2 arguments.
I know how to do this with functions and I suppose I could use some wrapper function that split the args into pairs and then combined the result (or just use 'reduce') but that seems wrong.
My question then is...
What is the idiomatic way to define a variadic multi-function in clojure?
There's nothing wrong with giving the defmulti dispatch function and defmethod bodies variadic signatures, and this is perfectly idiomatic:
(defmulti bar (fn [x & y] (class x)))
(defmethod bar String [x & y] (str "string: " (count y)))
(defmethod bar Number [x & y] (str "number: " (count y)))
(bar "x" 1 2 3) ;=> "string: 3"
(bar 1 2 3) ;=> "number: 2"

Get string name of passed in var in clojure?

How do I get the string name of a 2nd nested var like so?
(def bar "abc")
(defn string-var [foo]
(...))
(= "bar" (string-var bar))
You can do this with macro
(def bar "abc")
(defmacro string-var [foo]
(name foo))
(string-var bar)
=> "bar"

How do you make a callable object in Clojure?

How do you make a callable type or object in Clojure?
For example, how could I define a record Foo taking a single value :bar which could be called to print that value?
user=> (def foo (Foo. "Hello world"))
user=> (foo)
Hello World
user=> (:bar foo)
"Hello World"
(defrecord Foo [bar]
clojure.lang.IFn
(invoke [_] (println bar)))
((Foo. "Hello, world!"))
;; => Hello, world!
(:bar (Foo. "Hello, world!"))
;; => "Hello, world!"
...Whether doing this is a good idea is another question.
Records implementing IFn
(defrecord Foo [bar]
clojure.lang.IFn
(invoke [_] (println bar))
(applyTo [this args] (clojure.lang.AFn/applyToHelper this args)))