An idiomatic way to set default values in clojure is with merge:
;; `merge` can be used to support the setting of default values
(merge {:foo "foo-default" :bar "bar-default"}
{:foo "custom-value"})
;;=> {:foo "custom-value" :bar "bar-default"}
In reality however, often the default values are not simple constants but function calls. Obviously, I'd like to avoid calling the function if it's not going to be used.
So far I'm doing something like:
(defn ensure-uuid [msg]
(if (:uuid msg)
msg
(assoc msg :uuid (random-uuid))))
and apply my ensure-* functions like (-> msg ensure-uuid ensure-xyz).
What would be a more idiomatic way to do this? I'm thinking something like:
(merge-macro {:foo {:bar (expensive-func)} :xyz (other-fn)} my-map)
(associf my-map
[:foo :bar] (expensive-func)
:xyz (other-fn))
You can use delay combined with force.
You can then merge your defaults like
(merge {:foo "foo-default" :bar "bar-default" :uuid (delay (random-uuid))}
{:foo "custom-value" :uuid "abc"})
and access values using
(force (:foo ...))
or
(force (:uuid ...))
random-uuid will then only be called when you actually need the value (and only the first time).
You can wrap the call to force in a get-value function, or something like that.
I just adapted the condp macros and wrote the following:
(defmacro assoc-if-nil
"Takes a map as the first argument and a succession of key value pairs that
are used to set the key to value if the key of the map is nil. The value part
is only evaluated if the key is nil (thus different semantics to (merge)).
Example:
(assoc-if-nil {:a {:b :set}}
[:a :b] :non-def
[:a :c] :non-def
:d :non-def)
;; =>{:a {:b :set, :c :non-def}, :d :non-def}"
[m & clauses]
(assert (even? (count clauses)))
(let [g (gensym)
get-fn (fn[kork] (if (vector? kork) `get-in `get))
assoc-fn (fn[kork] (if (vector? kork) `assoc-in `assoc))
pstep (fn [[kork v]] `(if-not (~(get-fn kork) ~g ~kork)
(~(assoc-fn kork) ~g ~kork ~v)
~g))]
`(let [~g ~m ;; avoid double evaluation
~#(interleave (repeat g) (map pstep (partition 2 clauses)))]
~g)))
Which expands to:
(macroexpand-1 '
(assoc-if-nil m
[:a :b] :nested
:d :just-key))
(clojure.core/let
[G__15391 m
G__15391
(clojure.core/if-not
(clojure.core/get-in G__15391 [:a :b])
(clojure.core/assoc-in G__15391 [:a :b] :nested)
G__15391)
G__15391
(clojure.core/if-not
(clojure.core/get G__15391 :d)
(clojure.core/assoc G__15391 :d :just-key)
G__15391)]
G__15391)
Related
I have a map like so:
{:a "some" :b (str :a " stuff")}
What I'm trying to do is to have the :b value of the map be "some stuff", but the above example doesn't work. I tried wrapping it in def;
(def foo {:a "some" :b (str (:a foo) " stuff")})
But that doesn't work either. How do I make this work?
You have to use a let here (either define the map or the value of a). You can not access the map while it is build up.
also, you can employ macros for this case (for fun and education). Like you can make up simple anaphoric macro to do the trick:
(defmacro make-map-ana [& keyvals]
`(-> {}
~#(map (fn [[k v]] `((fn [~'it] (assoc ~'it ~k ~v))))
(partition-all 2 keyvals))))
that is how you use it:
user> (make-map-ana :a "some"
:b (str (:a it) " stuff"))
;;=> {:a "some", :b "some stuff"}
that gets expanded to clojure code this way:
(-> {}
((fn [it] (assoc it :a "some")))
((fn [it] (assoc it :b (str (:a it) " stuff")))))
so it gets reflective, passing the context (namely it) downstream.
(make-map-ana :a :fun
:b it
:c it)
;;=> {:a :fun, :b {:a :fun}, :c {:a :fun, :b {:a :fun}}}
another way to write this macro, is to use the overriding in let bindings:
(defmacro make-map-ana [& keyvals]
`(let [~'it {}
~#(mapcat (fn [[k v]] ['it `(assoc ~'it ~k ~v)])
(partition-all 2 keyvals))]
~'it))
now (make-map-ana :a "some" :b (str (:a it) " stuff")) would expand into this:
(let [it {}
it (assoc it :a "some")
it (assoc it :b (str (:a it) " stuff"))]
it)
also producing the result you need
Well, as cfrick said, I don't think you can access a map while you are building it like you wrote in your second example.
You could build a simple function like the one bellow.
(defn merge-a-b [map] (assoc map :b (str (:a map) (:b map))))
It's also useful if you end with a vector of those maps, like:
(def x2 [{:a "some", :b " stuff"} {:a "some-other", :b " stuff"}])
You can just use the map build-in in Clojure and run it like:
(map merge-a-b x2)
({:a "some", :b "some:a stuff"} {:a "some-other", :b "some-other stuff"})
Apparently get-in doesn't work for '() lists since they're not an associative data structure. This makes sense for the API and from the perspective of performance of large lists. From my perspective as a user it'd be great to still use this function to explore some small test data in the repl. For example I want to be able to:
(-> '({:a ("zero" 0)} {:a ("one" 1)} {:a ("two" 2)})
(get-in [1 :a 0]))
=> "one"
Is there some other function that works this way? Is there some other way to achieve this behavior that doesn't involve converting all my lists to (say) vectors?
This does what you ask:
(defn get-nth-in [init ks]
(reduce
(fn [a k]
(if (associative? a)
(get a k)
(nth a k)))
init
ks))
For example,
(-> '({:a "zero"} {:a "one"} {:a "two"})
(get-nth-in [1 :a]))
;"one"
and
(-> '({:a ("zero" 0)} {:a ("one" 1)} {:a ("two" 2)})
(get-nth-in [1 :a 0]))
;"one"
The extra 's you have get expanded into (quote ...):
(-> '({:a '("zero" 0)} {:a '("one" 1)} {:a '("two" 2)})
(get-nth-in [1 :a 0]))
;quote
Not what you intended, I think.
A post just yesterday had a problem regarding lazy lists and lazy maps (from clojure/data.xml). One answer was to just replace the lazy bits with plain vectors & maps using this function:
(defn unlazy
[coll]
(let [unlazy-item (fn [item]
(cond
(sequential? item) (vec item)
(map? item) (into {} item)
:else item))
result (postwalk unlazy-item coll)
]
result ))
Since the resulting data structure uses only vectors & maps, it works for your example with get-in:
(let [l2 '({:a ("zero" 0)} {:a ("one" 1)} {:a ("two" 2)})
e2 (unlazy l2) ]
(is= l2 e2)
(is= "one" (get-in e2 [1 :a 0] l2))
)
You can find the unlazy function in the Tupelo library.
The first param for get-in should be a map.
You have to figure out the feature of your sequence, use last, first, filter or some e.g. to get the element first
for example you could use (:a (last data))
Is there a convenient way in ClojureScript to pretty print a nested hash-map in the way that the whole tree-structure becomes immediately visible.
For instance a map like this
(def my-map {:a {:b 1 :c 9} :b {:d 8 :e {:f 2 :g 3 :h 4}} :c 10})
should be printed like this:
{:a {:b 1
:c 9}
:b {:d 8
:e {:f 2
:g 3
:h 4}}
:c 10}
EDIT: There might also be vectors in the map. The usecase is just to inspect larger data structures during development.
There is no built-in way to do it. You might come close to what you want by using cljs.pprint and setting cljs.pprint/*print-right-margin* to a low value.
I would recommend to take a look at a small library shodan which provides a very useful inspect function:
(require '[shodan.inspection :refer [inspect]])
(inspect {:aaaaaa 1
:bbbbbb {:ccc 2
:dddddd [1 2 3 4 5]}})
It won't print anything in your CLJS REPL but will provide a handy view in your browser's console:
You can collapse and expand nested datastructures - it basically does what you asked for.
As a personal challenge I wrote the following code:
(enable-console-print!)
(def atomic? (complement coll?))
(def padding #(apply str (repeat % " ")))
(def tabulate #(apply str (repeat % "\t")))
(def strcat #(->> (apply concat %&) (apply str)))
(defn my-max-key [x] (if (empty? x) [""] (apply (partial max-key count) x)))
(defn longest-key [m] (->> m keys (filter atomic?) (map str) my-max-key))
(def length (comp count str))
(def not-map? (complement map?))
(def nested? #(some coll? %))
(def join #(apply str (interpose % %2)))
(def join-lines (partial join "\n"))
(defn has-atomic? [coll] (some atomic? coll))
(defn diff-key-lengths [key1 key2] (- (length key1) (length key2)))
(defn convert
([thing] (convert -1 thing))
([depth thing]
(defn convert-items []
(defn convert-seq []
(conj []
(map (partial convert (inc depth)) thing)
""))
(defn string-horizontally [[key value]]
(str (tabulate (inc depth))
key
(padding (diff-key-lengths (longest-key thing) key))
" → "
value))
(defn string-vertically [[key value]]
(str (convert (inc depth) key) "\n"
(convert (+ 2 depth) "↓") "\n"
(convert (inc depth) value) "\n"))
(defn convert-kv [[key value]]
(if (nested? [key value])
(string-vertically [key value])
(string-horizontally [key value])))
(cond (atomic? thing)
[(str (tabulate depth) thing)]
(not-map? thing)
(convert-seq)
(map? thing)
(map convert-kv thing)))
(->> (convert-items) flatten join-lines)))
(def sample-input [["the first thing in this nested vector"]
{{"this is a key in a nested map"
"that points to me!!!"}
{"and that entire map points to this map!!!"
"cool!!!"
"but it gets cooler cause..."
"the value's line up!!!"}}])
(->> sample-input convert println)
The terminal output is (psst... the values in a map do line up but I don't think that chrome uses a monospaced font!):
Given a collection"
[{:key "key_1" :value "value_1"}, {:key "key_2" :value "value_2"}]
I would like to convert this to:
{"key_1" "value_1" "key_2" "value_2"}
An function to do this would be:
(defn long->wide [xs]
(apply hash-map (flatten (map vals xs))))
I might simplify this using the threading macro:
(defn long->wide [xs]
(->> xs
(map vals)
(flatten)
(apply hash-map)))
This still requires explicit definition of the function argument which I am not doing anything with other than passing to the first function. I might then rewrite this using comp to remove this:
(def long->wide
(comp (partial apply hash-map) flatten (partial map vals)))
This however requires repeated use of partial which to me is a lot of noise in the function.
Is there a some function in clojure that combines comp and ->> so I can create a higher order function without repeated use of partial, and also which out having to create a new function?
Since many of the answers here already don't answer the original question, but
suggest different approaches, I put that one back up too.
I'd go with reduce and destructuring:
(reduce
(fn [m {:keys [key value]}]
(assoc m key value))
{}
[{:key "key_1" :value "value_1"}, {:key "key_2" :value "value_2"}])
Note, that this will also work with string keys (which you mentioned in the comments) (note :strs):
(reduce
(fn [m {:strs [key value]}]
(assoc m key value))
{}
[{"key" "key_1" "value" "value_1"}, {"key" "key_2" "value" "value_2"}])
Another (point-free) version, when using keywords:
(partial (into {} (map (juxt :key :value))))
Since you mentioned in the comments, that you are using values from a DB, there might also be the chance, that you can switch to just return value tuples. Then the whole operation is just:
(into {} [["key_1" "value_1"]["key_2" "value_2"]])
Also note, that the use of vals on a map and expecting "insertion order" is
dangerous. Small maps are ordered only by accident:
user=> (take 3 (zipmap (range 3) (range 3)))
([0 0] [1 1] [2 2])
user=> (take 3 (zipmap (range 100) (range 100)))
([0 0] [65 65] [70 70])
An other alternative to the nice answers is also:
(apply hash-map (mapcat vals [{:key "key_1" :value "value_1"}, {:key "key_2" :value "value_2"}]))
or:
((comp #(apply hash-map %) #(mapcat vals %)) [{:key "key_1" :value "value_1"}, {:key "key_2" :value "value_2"}])
which are exactly the same.
As with clojure, so many ways to solve most problems.
(partial #(reduce (fn [r m] (assoc r (m :key) (m :value)))
{}
%)))
Not sure if the creation of anonymous functions violates your condition or not but this isn't adding functions to the namespace so I thought I'd throw it out there. This also has the benefit of not requiring the keys in the input maps to be keywords as :key and :value can be replaced with values of any type since the map is in the function position. For example:
(partial #(reduce (fn [r m] (assoc r (m "key") (m "value")))
{}
%)))
I want to create a map based on variable inputs where a key should only be present if its corresponding value is not nil.
Here's a toy example I came up with:
(defn make-map
[foo bar baz]
(-> {}
(into (and foo {:foo foo}))
(into (and bar {:bar bar}))
(into (and baz {:baz baz}))))
Is there a more accepted/idiomatic way to do this?
I think something like this is a bit more straightforward
(defn make-map
[foo bar baz]
(reduce (fn [m [k v]] (if (some? v) (assoc m k v) m))
{}
{:foo foo :bar bar :baz baz}))
user> (make-map 1 nil 2)
{:baz 2, :foo 1}
user> (make-map nil 1 2)
{:baz 2, :bar 1}
user> (make-map true false true)
{:baz true, :bar false, :foo true}
This uses cond-> to simplify things a little.
(defn make-map
[foo bar baz]
(cond-> {}
foo (assoc :foo foo)
bar (assoc :bar bar)
baz (assoc :baz baz)))
It's hard to tell with the toy example whether there's a better option for you.
(defn make-map [foo bar baz]
(into {}
(filter
#(if-not (nil? (second %)) { (first %) (second %)})
(map vector [ :foo :bar :baz] [for bar baz]))
)
)
For a little bit of variety, a generalisation using for:
(defn some-map
[& args]
(->> (for [[k v] (partition 2 args)
:when (some? v)]
[k v])
(into {})))
Usage:
(some-map :a 1 :b 2 :c nil :d false)
;; => {:a 1, :b 2, :d false}
Or, akin to #noisesmith's answer, something to be applied to an existing map:
(defn some-map
[m]
(into {} (filter (comp some? val) m)))
(some-map {:a 1 :b 2 :c nil :d false})
;; => {:b 2, :d false, :a 1}
You could abstract this to use a syntax and application similar to zipmap so you can have variable argument lists for both keys and args
(defn when-zip
[keys args]
(->> args
(map vector keys)
(remove (comp not second))
(into {})))
(when-zip [:foo :bar :baz :qux] [true nil false 1])
=> {:qux 1, :foo true}
When you don't like the creation of intermediate lazy results you can use Clojure 1.7's transducers or blatantly rip off zipmap's source
(defn when-zip
"Returns a map with each of the keys mapped to
the corresponding val when val is truthy."
[keys vals]
(loop [map {}
ks (seq keys)
vs (seq vals)]
(if (and ks vs)
(recur (if-let [v (first vs)]
(assoc map (first ks) v)
map)
(next ks)
(next vs))
map)))
(when-zip [:foo :bar :baz :qux] [true nil false 1])
=> {:qux 1, :foo true}
If you really still need the original syntax you could then use this to define specific versions
(defn make-map
[& args]
(when-zip [:foo :bar :baz :qux] args))
(make-map true nil false 1)
=> {:qux 1, :foo true}
On the other hand, you could just not bother with removing nils and use zipmap; when you do a map lookup on a non-existing key further on, it will give the same result as a key with value nil anyway:
(:baz {:qux 1, :foo true})
=> nil
(:baz {:qux 1, :baz nil, :bar false :foo true})
=> nil
Of course, this is different with :bar. But usually it's better to do nil and false punning at the consuming stage instead of during transformation.
Just for completeness, here's something closer to what I was trying to reach for originally but didn't quite get.
(defn make-map
[foo bar baz]
(apply hash-map
(concat
(and foo [:foo foo])
(and bar [:bar bar])
(and baz [:baz baz]))))