How to set default values for fields in records in Clojure? - 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"}

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}

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

Define clojure hash map without duplicating tokens

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

Can I refer to a clojure hashmap value from another value in the same map?

I'm trying to come up with some way for the values in a clojure hashmap to refer to each other. Conceptually, something like this:
(def m {:a 1 :b 5 :c (+ (:a m) (:b m))} ;Implies (= (:c m) 6)
This doesn't work, of course since I'm circularly referencing m. I can do something like
(def m {:a 1 :b 5 :c (fn [a b] (+ a b))})
((:c m) (:a m) (:b m)) ;=> 6
but that doesn't really gain anything because I still have to know which a and b to put into the function. Another attempt:
(def m {:a 1 :b 5 :c (fn [m] (+ (:a m) (:b m)))})
((:c m) m) ;=> 6
It's a bit better since I've now internalized the function to a map though not specifically this map. I might try to fix that with something like this
(defn new-get [k m]
(let [v-or-fn (get m k)]
(if (fn? v-or-fn) (v-or-fn m) v-or-fn)))
(def m {:a 1 :b 5 :c (fn [m] (+ (:a m) (:b m)))})
(new-get :a m) ;=> 1
(new-get :b m) ;=> 5
(new-get :c m) ;=> 6
I think this is about the best I can do. Am I missing something more clever?
Couldn't help myself from writing a macro:
(defmacro defmap [name m]
(let [mm (into [] (map (fn [[k v]] `[~k (fn [~name] ~v)]) m))]
`(def ~name
(loop [result# {} mp# (seq ~mm)]
(if (seq mp#)
(let [[k# v#] (first mp#)]
(recur (assoc result# k# (v# result#)) (rest mp#)))
result#)))))
(defmap m [[:a 1]
[:b 5]
[:c (+ (:a m) (:b m))]])
;; m is {:a 1 :b 5 :c 6}
As I've already said in comment above you can use let form:
(def m
(let [a 1 b 5]
{:a a :b b :c (+ a b)}))
This should be fine if you're using values that known only inside m definition. Otherwise you would better to use function parameters as #Michiel shown.
P.S. by the way you're free to use everything inside def you're usually use in clojure. Moreover, sometimes you're free to use let in sugared form inside some other forms (although this let uses different mechanisms than usual let form):
(for [x (...) xs]
:let [y (+ x 1)]
; ...
Since c is a derived value, so a function, of a and b you're probably better of by defining a function that produces this map:
(defn my-map-fn [a b]
{:a a :b b :c (+ a b)})
(def my-map (my-map-fn 1 2))
(:c my-map) ;;=> 3
Here is my take on it:
(defmacro let-map [& bindings]
(let [symbol-keys (->> bindings (partition 2) (map first))]
`(let [~#bindings]
(into {} ~(mapv (fn [k] [(keyword k) k]) symbol-keys)))))
;; if you view it as similar to let, when it's more complicated:
(let-map
a 1
b 5
c (+ a b)) ; => {:a 1, :b 5, :c 6}
;; if you see it as an augmented hash-map, when it's simple enough:
(let-map a 1, b 5, c (+ a b)) ; => {:a 1, :b 5, :c 6}