Define clojure hash map without duplicating tokens - clojure

I'd like to return a hash-map like so:
(fn [foo bar] {:foo foo :bar bar})
Is it possible to do that without repeating the names? Something like how let allows this:
(let [{:keys [foo bar]} args]
(...))

The macro:
(defmacro some-hash-thing [& vals]
(zipmap (map keyword vals) vals))
And in use:
(let [a 4, b 5]
(some-hash-thing b a))
;; => {:a 4, :b 5}

(defmacro as-keymap [& names] `(conj {} ~#(map (juxt keyword symbol) names)))

Related

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}

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)

Required keys when destructuring

Is there a way to have an exception raised if the keys you're trying to destructure aren't in the map passed to your function? Would this be a good use case for a macro?
For example:
(defn x [{:keys [a b]}] (println a b))
I'd like this to work:
(x {:a 1 :b 2})
But this to raise an exception (:b is missing)
(x {:a 1})
What about a pre-condition?
(defn x [{:keys [a b]}]
{:pre [(some? a) (some? b)]}
(println a b))
user=> (x {:a 1})
AssertionError Assert failed: (some? b) user/x (NO_SOURCE_FILE:1)
Edit: Yes, you could use a macro to handle this for you. Maybe something like this:
(defmacro defnkeys [name bindings & body]
`(defn ~name [{:keys ~bindings}]
{:pre ~(vec (map #(list 'some? %) bindings))}
~#body))
(defnkeys foo [a b]
(println a b))
(foo {:a 1 :b 2})
(foo {:a 1}) ;; AssertionError Assert failed: (some? b)
(foo {:a 1 :b nil}) ;; AssertionError Assert failed: (some? b)

Clojure idiom for dynamic map creation

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

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"}