make-keyword-map in Clojure - Idiomatic? - clojure

I have been writing some Clojure recently, and I found myself using the following pattern frequently enough:
(let [x (bam)
y (boom)]
{:x x
:y y})
So I went ahead and wrote the following macro:
(defmacro make-keyword-map [& syms]
`(hash-map ~#(mapcat (fn [s] [(keyword (name s)) s]) syms)))
With that, code now looks like:
(let [x (bam)
y (boom)]
(make-keyword-map x y)
Would this sort of macro be considered idiomatic? Or am I doing something wrong, and missing some already established pattern to deal with something of this sort?

Note, that you can also replace all of:
(let [x (bam)
y (boom)]
{:x x
:y y})
with just:
{:x (bam)
:y (boom)}
which will evaluate to the same thing.
If your let expressions depend on one another, then how about a macro like so:
(defmacro make-keyword-map [& let-body]
(let [keywords-vals (flatten (map (juxt keyword identity)
(map first (partition 2 let-body))))]
`(let ~(vec let-body)
(hash-map ~#keywords-vals))))
that way (make-keyword-map x (foo 1) y (bar 2) z (zoom x)) expands to:
(clojure.core/let [x (foo 1) y (bar 2) z (zoom x)]
(clojure.core/hash-map :x x :y y :z z))
So something like this would work:
user=> (defn foo [x] (+ x 1))
#'user/foo
user=> (defn bar [x] (* x 2))
#'user/bar
user=> (defn zoom [x] [(* x 100) "zoom!"])
#'user/zoom
user=> (make-keyword-map x (foo 1) y (bar 2) z (zoom x))
{:z [200 "zoom!"], :y 4, :x 2}
Not sure how idiomatic that is, but it also saves you a let, compared to your original example.

Related

How to get an element in a matrix in clojure

I have a vector of vectors [[plate,'p1',0,1],[plate,'p2',0,2],[plate,'p3',1,1]] containing x,y positions of detected plates.
How do I retrieve the x position of plate p3?
It seems to be a simple task but I'm more familiar with python, so I'm not sure how to do this in clojure.
i would go with something like this:
(def data [[:plate "p1" 0 1] [:plate "p2" 0 2] [:plate "p3" 1 1]])
(some (fn [[_ v x y]] (when (= v "p3") [x y])) data)
;;=> [1 1]
(some (fn [[_ v x y]] (when (= v "p123") [x y])) data)
;;=> nil
(def p '[[plate,"p1",0,1],[plate,"p2",0,2],[plate,"p3",1,1]])
;; be aware, 'p1' you can use in Python, but because in Clojure `'` at beginning
;; of a symbol is parsed as `quote`, you can't use `''` instead of `""` in contrast to Python.
;; create a nested map out of the vec of vecs
(defn vecs-to-map [vecs]
(reduce (fn [m [_ id x y]] (assoc m id {:x x :y y}))
{}
vecs))
(def m (vecs-to-map p))
;;=> {"p1" {:x 0, :y 1}, "p2" {:x 0, :y 2}, "p3" {:x 1, :y 1}}
;; you can access such a nested list via `get-in` and giving the nested map
;; and the keys it should apply on it.
(get-in m ["p3" :x])
;;=> 1
Since the irregularity that one key is a string and the other a keyword is
not so nice, I would make out of them all keywords:
(defn vecs-to-map [vecs]
(reduce (fn [m [_ id x y]] (assoc m (keyword id) {:x x :y y}))
{}
vecs))
(def m (vecs-to-map p))
;; access it by:
(get-in m [:p3 :x])
;;=> 1
Additional Thoughts
We ignored the first element of the vec plate.
Let's say there exist also another vectors like
(def b '[[box "b1" 0 1] [box "b2" 0 2] [box "b3" 1 1]])
And if we want a nested map which contains :plate and :box in the
outer level as keys, we have to change the vecs-to-map function.
(defn vecs-to-map [vecs]
(reduce (fn [m [k id x y]] (assoc m (keyword k)
(assoc (get m (keyword k) {})
(keyword id) {:x x :y y})))
{}
vecs))
Then we can generate the map containing everything by:
(def m (vecs-to-map (concat p b)))
;; or alternatively in two steps:
;; (def m (vecs-to-map p))
;; (def m (merge m (vecs-to-map b)))
m
;; => {:plate {:p1 {:x 0, :y 1}, :p2 {:x 0, :y 2}, :p3 {:x 1, :y 1}}, :box {:b1 {:x 0, :y 1}, :b2 {:x 0, :y 2}, :b3 {:x 1, :y 1}}}
And we access the content by:
;; access through:
(get-in m [:plate :p3 :x])
;; => 1
(get-in m [:box :b2 :y])
;; => 2
You don't really provide much context on what you're trying to do but it feels like you want to filter the vector of tuples to those that have the symbol p3' in the second position and then return just the third and fourth elements of such a match?
If so, the following would work:
dev=> (def plate :plate)
#'dev/plate
dev=> (def data [[plate,'p1',0,1],[plate,'p2',0,2],[plate,'p3',1,1]])
#'dev/data
dev=> (let [[[_ _ x y]] (filter (comp #{'p3'} second) data)]
#_=> [x y])
[1 1]
This doesn't feel very idiomatic, so perhaps you could explain more of the context?
Note: 'p3' is a symbol whose name is p3' so I wonder if you mean "p3" for a string?
The vector of vector format doesn't seem very conducive to the sort of access you want to perform - perhaps changing it to a hash map, whose keys are the plate IDs (if that's what p1, p2, and p3 are?) would be better to work with?
Edit: in response to #leetwinkski's note about the result when there is no match, here's an alternative:
You could use when-first:
dev=> (when-first [[_ _ x y] (filter (comp #{'p123'} second) data)]
#_=> [x y])
nil
dev=> (when-first [[_ _ x y] (filter (comp #{'p3'} second) data)]
#_=> [x y])
[1 1]
Here is how I would do it, based on my favorite template project. Please also note that in Clojure strings always use double-quotes like "p1". Single quotes are totally different!
(ns tst.demo.core
(:use tupelo.core tupelo.test))
(defn vec-has-label
"Returns true iff a vector has the indicated label"
[vec lbl]
(= lbl (nth vec 1)))
(defn vec->x
"Given a vector, return the x value"
[vec]
(nth vec 2))
(defn find-label
[matrix label]
(let [tgt-vecs (filterv #(vec-has-label % label) matrix) ; find all matching vectors
x-vals (mapv vec->x tgt-vecs)] ; extract the x values
x-vals))
The unit tests show the code in action
(dotest
(isnt (vec-has-label '[plate "p1" 0 1] "p0"))
(is (vec-has-label '[plate "p1" 0 1] "p1"))
(is= 9 (vec->x '[plate "p1" 9 1]))
(let [matrix (quote
[[plate "p1" 0 1]
[plate "p2" 0 2]
[plate "p3" 1 1]])]
(is= (find-label matrix "p3")
[1])
))
The unit test show the 2 ways of "quoting" a data structure that contains one or more symbols. This would be unneeded if the redundant plate symbol weren't present.
See also this list of documentation sources.

quick way to apply hash-map to anonymous function in clojure

My question is whether given a hashmap
(def my-map {'x 1 'y 2 'z})
I can apply it to an anonymous function,
(fn [x y z] (+ x (* y z))
so that the arguments match the keys in the map, somthing like
(apply-ish my-map (fn [x y z] (+ x (* y z)))
Is there an easy fix to this problem? I feel like there is but I cant figure it out.
You can use map destructuring:
user> (def my-map {'x 1 'y 2 'z 3})
#'user/my-map
user> ((fn [{x 'x y 'y z 'z}] (+ x (* y z))) my-map)
7
You can simplify a bit with this form of desctructuring:
user> ((fn [{:syms [x y z]}] (+ x (* y z))) my-map)
7
or if you use keywords for your map keys:
user> (def my-map2 {:x 1 :y 2 :z 3})
#'user/my-map2
user> ((fn [{:keys [x y z]}] (+ x (* y z))) my-map2)
7
personally, i would not modify the function to accept the map as the arg, since it makes the function itself way less generic. The alternative (and idiomatic i guess, for any language) solution is to select needed keys from the map before passing them to function. That is quite easy, since both map and symbol (and keyword too) have function semantics in clojure:
user> (apply f (map my-map ['x 'y 'z]))
;;=> 7
user> (apply f ((juxt 'x 'y 'z) my-map))
;;=> 7

How to explain the &env example in book <Clojure Programming>

There's an example in the book (P252), it's about the &env in macro, I just don't understand how to get the result{x 1, y 2}
(defmacro spy-env []
(let [ks (keys &env)]
`(prn (zipmap '~ks [~#ks]))))
(let [x 1 y 2]
(spy-env)
(+ x y))
; {x 1, y 2}
;= 3
I tried in the REPL like this:
user=> (defmacro spy-env [] (let [ks (keys &env)] `(prn ~#(keys &env))))
#'user/spy-env
user=> (let [x 1 y 2] (spy-env))
2 1
It's wried.
Expanding the macro would be a great way for you to see how it works, but unfortunately macroexpand-1 doesn't convey the right bindings for &env. This example:
(let [x 1 y 2] (spy-env))
Actually expands out to:
(let [x 1 y 2] (prn (zipmap '(x y) [x y])))
Do you get it? Within the first sequence, x and y are not evaluated, but within the second, they are. So it zips the symbols to whatever they evaluate to. Within that let block, (zipmap '(x y) [x y]) evaluates to {x 1 y 2}.
a) If you try the code of the book it does output the right value
user=> (defmacro spy-env []
#_=> (let [ks (keys &env)]
#_=> `(prn (zipmap '~ks [~#ks]))))
#'user/spy-env
user=> (let [x 1 y 2] (spy-env))
{x 1, y 2}
nil
user=> (let [x 1 y 2]
#_=> (spy-env)
#_=> (+ x y))
{x 1, y 2}
3
b) now your code is different
initial code :
`(prn (zipmap '~ks [~#ks]))
vs
`(prn ~#(keys &env))
with actually #(keys &env) being equal to #ks

How to increment by a number in Clojure?

I would like to know how to increment by X amount a number, in other languages I used to do
foo += 0.1;
but I have not idea how it can be done in Clojure
Variables are immutable in Clojure. So you should not try to change the value of foo, but instead, "create" a new foo:
(def foo2 (+ foo 0.1))
...or, if in a loop, recur with a new value:
(loop [foo 5.0]
(when (< foo 9)
(recur (+ foo 0.1))))
...or, if foo is an atom, swap! it with a new value:
(def foo (atom 5.0))
(swap! foo (partial + 0.1))
I recommend you start by reading the rationale of Clojure.
Blacksad's answer covers defining vars so I would just like to add the other scopes in which you may wish to have a value that is incremented from another value:
within a function's local scope:
user> (defn my-function [x]
(let [y (inc x)
z (+ x y)]
[x y z]))
#'user/my-function
user> (my-function 4)
[4 5 9]
and If you want to build a value incrementally to make the process more clear:
user> (defn my-function [x]
(let [y (inc x)
z (+ x y)
z (+ z 4)
z (* z z)]
[x y z]))
#'user/my-function
user> (my-function 4)
[4 5 169]
This can make the process more presentable, though it is not a "way to get variables back" and is really only useful in limited contexts. This pattern is used in clojure.core's threading macros.

Trouble with clojure quote-paren `( ... ) macro

For practice, I've defined
(defmacro quote-paren
"body -> `(body)"
[& body]
`(~#body))
which has the expected transformation (quote-paren body) => ``(body)`. It seems to satisfy a few basic tests:
user=> (macroexpand-1 `(quote-paren 3 4 5))
(3 4 5)
user=> (macroexpand-1 `(quote-paren println "hi"))
(clojure.core/println "hi")
user=> (macroexpand-1 `(quote-paren (println "hi")))
((clojure.core/println "hi"))
However, I've been testing it with this do-while macro (modified from here):
(defmacro do-while
[test & body]
(quote-paren loop []
~#body
(when ~test
(recur))))
(def y 4)
(do-while (> y 0)
(def y (dec y)))
But the result is
IllegalStateException Attempting to call unbound fn: #'clojure.core/unquote-splicing clojure.lang.Var$Unbound.throwArity (Var.java:43)
I don't understand this, because from what I can see the `quote-paren' macro works fine (with ~#body plugged in):
user=> (macroexpand-1
`(quote-paren loop []
(def y (dec y))
(when ~test
(recur))))
(clojure.core/loop [] (def user/y (clojure.core/dec user/y)) (clojure.core/when #<core$test clojure.core$test#1f07f672> (recur)))
But trying to macroexpand do-while causes an "unbound fn". Is there something subtle I'm missing?
missing the syntax-quote before quote-paren
user> (defmacro do-while
[test & body]
`(quote-paren loop []
~#body
(when ~test
(recur))))
#'user/do-while
which then expands properly:
user> (macroexpand '(do-while (> y 0)
(def y (dec y))))
(loop* [] (def y (dec y)) (clojure.core/when (> y 0) (recur)))
and seems to work:
user> (def y 4)
#'user/y
user> (do-while (> y 0)
(def y (dec y)))
nil
user>