Clojure idiom for dynamic map creation - clojure

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]))))

Related

merge to set default values, but potentially expensive functions

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)

Remove nil values from deeply nested maps

Given this map:
{:a nil
:b {:c nil
:d 2
:e {:f nil
:g 4}}}
I need a function to remove all nil values, so that the returned map is
{:b {:e {:g 4}
:d 2}}
Or, when given:
{:a nil
:b {:c nil
:d nil
:e {:f nil
:g nil}}}
The result is:
nil
This question has an answer containing a function that supposedly works for nested maps, but that function fails when given a map that is nested more than one level deep.
modification of answer from here https://stackoverflow.com/a/22186735/1393248
(defn remove-nils
"remove pairs of key-value that has nil value from a (possibly nested) map. also transform map to nil if all of its value are nil"
[nm]
(clojure.walk/postwalk
(fn [el]
(if (map? el)
(not-empty (into {} (remove (comp nil? second)) el))
el))
nm))
(defn clean [m]
(if (map? m)
(let [clean-val (fn [[k v]]
(let [v' (clean v)]
(when-not (nil? v')
[k v'])))
m' (->> (map clean-val m)
(remove nil?)
(into {}))]
(when-not (empty? m') m'))
m))
Using specter you can do it like this:
(declarepath DEEP-MAP-VALS)
(providepath DEEP-MAP-VALS (if-path map? [(compact MAP-VALS) DEEP-MAP-VALS] STAY))
(setval [DEEP-MAP-VALS nil?] NONE
{:a nil
:b {:c nil
:d 2
:e {:f nil
:g 4}}})
Please note it will return :com.rpl.specter.impl/NONE instead of nil if nothing is left.
This is partial reuse of this answer

using doseq with bool function

I'm new to Clojure.
Lets say I have a simple doseq code:
(doseq [keyval db] (f keyval))
f is a Bool function, and I want to know if all the iterations returned TRUE.
It's just like evaluating and operator for each sequence.
How can I check the results for each iteration and use it after the doseq ,
and what is the best way to do it?
Example:
(and (f? :a db) (f? :b db)...)
doseq is for when the body is just intended to produce side effects per element.
Your example should be implemented with every?:
(every? f db)
There are many ways to map and filter a collection to search for different things, hopefully these examples give you some ideas:
user> (def db {:a 1 :b 2 :c 3 :d 4})
#'user/db
user> (map (fn [[k v]] (if (even? v) true false)) db)
(false false true true)
user> (filter (fn [[k v]] (if (even? v) true false)) db)
([:b 2] [:d 4])
There are several ways to see if they are all true:
user> (reduce #(and %1 %2)
(map (fn [[k v]] (if (even? v) true false))
{:a 2 :b 4 :c 6}))
true
user> (reduce #(and %1 %2) (map (fn [[k v]] (if (even? v) true false)) db))
false
user> (not-any? false? (map (fn [[k v]] (if (even? v) true false)) db))
false
user> (not-any? false? (map (fn [[k v]] (if (even? v) true false)) {:a 2 :b 4 :c 6}))
true
And look at the db for other things:
user> (filter (fn [[k v]] (if (even? v) true false)) {:a 2 :b 4 :c 6})
([:a 2] [:c 6] [:b 4])
user> (filter (fn [[k v]] (if (odd? v) true false)) {:a 2 :b 4 :c 6})
()
user> (if (empty? (filter (fn [[k v]] (if (odd? v) true false)) {:a 2 :b 4 :c 6})) "all where even" "some where odd")
"all where even"
You don't need to evaluate every term but only up until the first false. If I get what you are asking for, try some.
; assuming f? and db in scope
(defn all-are-f [aseq] (not (some #(not (f? % db)) aseq)))

How to convert map to URL query string in Clojure/Compojure/Ring?

In Clojure / Compojure, how do I convert a map to a URL query string?
{:foo 1 :bar 2 :baz 3}
to
foo=1&bar=2&baz=3
Is there any utility method to do this in compojure?
Yes, there is a utility for this already that doesn't involve Hiccup or rolling your own string/join/URLEncoder function:
user=> (ring.util.codec/form-encode {:foo 1 :bar 2 :baz 3})
"foo=1&bar=2&baz=3"
user=>
Compojure depends on ring/ring-core, which includes ring.util.codec, so you already have it.
Something like:
(defn params->query-string [m]
(clojure.string/join "&" (for [[k v] m] (str (name k) "=" v))))
should do it...
REPL session:
user> (defn params->query-string [m]
(clojure.string/join "&"
(for [[k v] m]
(str (name k) "=" (java.net.URLEncoder/encode v)))))
#'user/params->query-string
user> (params->query-string {:foo 1 :bar 2 :baz 3})
"foo=1&bar=2&baz=3"
user>
(defn to-query [inmap]
(->> inmap
(map (fn [[f s]] (str (name f) "=" (java.net.URLEncoder/encode (str s) "UTF-8"))))
(clojure.string/join '&)
))
This code removes ':' from keywords, but will throw exception if keywords are numbers.
(to-query {:foo 1 :bar 2 :baz 3})
=> "foo=1&bar=2&baz=3"

How to set default values for fields in records in Clojure?

I am creating records in Clojure and would like to set some fields up with a default value. How can I do this?
Use a constructor function.
(defrecord Foo [a b c])
(defn make-foo
[& {:keys [a b c] :or {a 5 c 7}}]
(Foo. a b c))
(make-foo :b 6)
(make-foo :b 6 :a 8)
Of course there are various variations. You could for example require certain fields to be non-optional and without a default.
(defn make-foo
[b & {:keys [a c] :or {a 5 c 7}}]
(Foo. a b c))
(make-foo 6)
(make-foo 6 :a 8)
YMMV.
You can pass initial values to a record pretty easily when you construct it though an extension map:
(defrecord Foo [])
(def foo (Foo. nil {:bar 1 :baz 2}))
In light of this, I usually create a constructor function that merges in some default values (which you can override as you want):
(defn make-foo [values-map]
(let [default-values {:bar 1 :baz 2}]
(Foo. nil (merge default-values values-map))))
(make-foo {:fiz 3 :bar 8})
=> #:user.Foo{:fiz 3, :bar 8, :baz 2}
After having the same question, I ended up wrapping the defrecord and the factory function up into a single definition using a macro.
The macro:
(defmacro make-model
[name args & body]
(let [defaults (if (map? (first body)) (first body) {})
constructor-name (str/lower-case (str "make-" name))]
`(do (defrecord ~name ~args ~#(if (map? (first body)) (rest body) body))
(defn ~(symbol constructor-name)
([] (~(symbol constructor-name) {}))
([values#] (~(symbol (str "map->" name)) (merge ~defaults values#)))))))
Usage
(make-model User [firstName lastName] {:lastName "Smith"})
=> #'user/make-user
(make-user {:firstName "John"})
=> #user.User{:firstName "John", :lastName "Smith"}