I'm not quite understanding what's wrong about
(defrecord Person [name age])
(defn make-person [& opts]
(let [defaults {:age 18}]
(map->Person (merge defaults opts))))
(make-person {:name "Jim"})
=> ClassCastException clojure.lang.PersistentArrayMap cannot be cast to java.util.Map$Entry clojure.lang.APersistentMap.cons (APersistentMap.java:42)
If I do:
(map->Person (merge {:age 18} {:name "Jim"}))
I can also get the make-person function working with a non optional argument.
(defn make-person [opts]
(let [defaults {:age 18}]
(map->Person (merge defaults opts))))
The solution I've settled on for what I want to do works like:
(defn make-person
([opts] (map->Person (merge {:age 18} opts)))
([] (map->Person {:age 18})))
So I guess I'm asking, what does & really do when destructuring function arguments?
The & opts returns a sequence, which is problematic if you pass a map in.
You probably want to destructure the sequence something like:
(defn make-person [& [opts]]
(map->Person (merge {:age 18} opts))
Which lets you do (make-person) or (make-person {:opt1 "foo" :opt2 "bar"})
you could take advantage of the special syntax to allow keyword args:
(defn make-person [ & {:as opts} ]
(map->Person (merge {:age 18} opts))
Allowing you to do (make-person :opt1 "foo" :opt2 "bar") but in my experience that makes calling make-person difficult if you want to do the merging outside the call (which you will one day) (i.e if you want (make-person (merge some-opts some-other-opts)
Related
Let's say I have several vectors
(def coll-a [{:name "foo"} ...])
(def coll-b [{:name "foo"} ...])
(def coll-c [{:name "foo"} ...])
and that I would like to see if the names of the first elements are equal.
I could
(= (:name (first coll-a)) (:name (first coll-b)) (:name (first coll-c)))
but this quickly gets tiring and overly verbose as more functions are composed. (Maybe I want to compare the last letter of the first element's name?)
To directly express the essence of the computation it seems intuitive to
(apply = (map (comp :name first) [coll-a coll-b coll-c]))
but it leaves me wondering if there's a higher level abstraction for this sort of thing.
I often find myself comparing / otherwise operating on things which are to be computed via a single composition applied to multiple elements, but the map syntax looks a little off to me.
If I were to home brew some sort of operator, I would want syntax like
(-op- (= :name first) coll-a coll-b coll-c)
because the majority of the computation is expressed in (= :name first).
I'd like an abstraction to apply to both the operator & the functions applied to each argument. That is, it should be just as easy to sum as compare.
(def coll-a [{:name "foo" :age 43}])
(def coll-b [{:name "foo" :age 35}])
(def coll-c [{:name "foo" :age 28}])
(-op- (+ :age first) coll-a coll-b coll-c)
; => 106
(-op- (= :name first) coll-a coll-b coll-c)
; => true
Something like
(defmacro -op-
[[op & to-comp] & args]
(let [args' (map (fn [a] `((comp ~#to-comp) ~a)) args)]
`(~op ~#args')))
Is there an idiomatic way to do this in clojure, some standard library function I could be using?
Is there a name for this type of expression?
For your addition example, I often use transduce:
(transduce
(map (comp :age first))
+
[coll-a coll-b coll-c])
Your equality use case is trickier, but you could create a custom reducing function to maintain a similar pattern. Here's one such function:
(defn all? [f]
(let [prev (volatile! ::no-value)]
(fn
([] true)
([result] result)
([result item]
(if (or (= ::no-value #prev)
(f #prev item))
(do
(vreset! prev item)
true)
(reduced false))))))
Then use it as
(transduce
(map (comp :name first))
(all? =)
[coll-a coll-b coll-c])
The semantics are fairly similar to your -op- macro, while being both more idiomatic Clojure and more extensible. Other Clojure developers will immediately understand your usage of transduce. They may have to investigate the custom reducing function, but such functions are common enough in Clojure that readers can see how it fits an existing pattern. Also, it should be fairly transparent how to create new reducing functions for use cases where a simple map-and-apply wouldn't work. The transducing function can also be composed with other transformations such as filter and mapcat, for cases when you have a more complex initial data structure.
You may be looking for the every? function, but I would enhance clarity by breaking it down and naming the sub-elements:
(let [colls [coll-a coll-b coll-c]
first-name (fn [coll] (:name (first coll)))
names (map first-name colls)
tgt-name (first-name coll-a)
all-names-equal (every? #(= tgt-name %) names)]
all-names-equal => true
I would avoid the DSL, as there is no need and it makes it much harder for others to read (since they don't know the DSL). Keep it simple:
(let [colls [coll-a coll-b coll-c]
vals (map #(:age (first %)) colls)
result (apply + vals)]
result => 106
I don't think you need a macro, you just need to parameterize your op function and compare functions. To me, you are pretty close with your (apply = (map (comp :name first) [coll-a coll-b coll-c])) version.
Here is one way you could make it more generic:
(defn compare-in [op to-compare & args]
(apply op (map #(get-in % to-compare) args)))
(compare-in + [0 :age] coll-a coll-b coll-c)
(compare-in = [0 :name] coll-a coll-b coll-c)
;; compares last element of "foo"
(compare-in = [0 :name 2] coll-a coll-b coll-c)
I actually did not know you can use get on strings, but in the third case you can see we compare the last element of each foo.
This approach doesn't allow the to-compare arguments to be arbitrary functions, but it seems like your use case mainly deals with digging out what elements you want to compare, and then applying an arbitrary function to those values.
I'm not sure this approach is better than the transducer version supplied above (certainly not as efficient), but I think it provides a simpler alternative when that efficiency is not needed.
I would split this process into three stages:
transform items in collections into the data in collections you want to operate
on - (map :name coll);
Operate on transformed items in collections, returning collection of results - (map = transf-coll-a transf-coll-b transf-coll-c)
Finally, selecting which result in resulting collection to return - (first calculated-coll)
When playing with collections, I try to put more than one item into collection:
(def coll-a [{:name "foo" :age 43} {:name "bar" :age 45}])
(def coll-b [{:name "foo" :age 35} {:name "bar" :age 37}])
(def coll-c [{:name "foo" :age 28} {:name "bra" :age 30}])
For example, matching items by second char in :name and returning result for items in second place:
(let
[colls [coll-a coll-b coll-c]
transf-fn (comp #(nth % 1) :name)
op =
fetch second]
(fetch (apply map op (map #(map transf-fn %) colls))))
;; => false
In transducers world you can use sequence function which also works on multiple collections:
(let
[colls [coll-a coll-b coll-c]
transf-fn (comp (map :name) (map #(nth % 1)))
op =
fetch second]
(fetch (apply sequence (map op) (map #(sequence transf-fn %) colls))))
Calculate sum of ages (for all items at the same level):
(let
[colls [coll-a coll-b coll-c]
transf-fn (comp (map :age))
op +
fetch identity]
(fetch (apply sequence (map op) (map #(sequence transf-fn %) colls))))
;; => (106 112)
I'm learning Clojure and enjoying it but find an inconsistency in Records that puzzles me: why doesn't the default map constructor (map->Whatever) check for data integrity when creating a new Record? For instance:
user=> (defrecord Person [first-name last-name])
#<Class#46ffda99 user.Person>
user=> (map->Person {:first-name "Rich" :last-name "Hickey"})
#user.Person {:first-name "Rich" :last-name "Hickey"}
user=> (map->Person {:first-game "Rich" :last-name "Hickey"})
#user.Person {:first-game "Rich" :first-name nil :last-name "Hickey"}
I believe the Map is not required to define all the fields in the Record definition and it is also allowed to contain extra fields that aren't part of the Record definition. Also I understand that I can define my own constructor which wraps the default constructor and I think a :post condition can then be used to check for correct (and comprehensive) Record creation (have not been successful in getting that to work).
My question is: Is there an idiomatic Clojure way to verify data during Record construction from a Map? And, is there something that I'm missing here about Records?
Thank you.
I think your comprehensiveness requirement is already quite specific, so nothing built-in I know of covers this.
One thing you can do nowadays is use clojure.spec to provide an s/fdef for your constructor function (and then instrument it).
(require '[clojure.spec.alpha :as s]
'[clojure.spec.test.alpha :as stest])
(defrecord Person [first-name last-name])
(s/fdef map->Person
:args (s/cat :map (s/keys :req-un [::first-name ::last-name])))
(stest/instrument `map->Person)
(map->Person {:first-name "Rich", :last-name "Hickey"})
(map->Person {:first-game "Rich", :last-name "Hickey"}) ; now fails
(If specs are defined for ::first-name and ::last-name those will be checked as well.)
Another option is to use Plumatic Schema to create a wrapper "constructor" function specifying the allowed keys. For example:
(def FooBar {(s/required-key :foo) s/Str (s/required-key :bar) s/Keyword})
(s/validate FooBar {:foo "f" :bar :b})
;; {:foo "f" :bar :b}
(s/validate FooBar {:foo :f})
;; RuntimeException: Value does not match schema:
;; {:foo (not (instance? java.lang.String :f)),
;; :bar missing-required-key}
The first line defines a schema that accepts only maps like:
{ :foo "hello" :bar :some-kw }
You wrapper constructor would look something like:
(def NameMap {(s/required-key :first-name) s/Str (s/required-key :last-name) s/Str})
(s/defn safe->person
[name-map :- NameMap]
(map->Person name-map))
or
(s/defn safe->person-2
[name-map]
(assert (= #{:first-name :last-name} (set (keys name-map))))
(map->Person name-map))
Is there a way to retrieve the metadata of the arguments inside a clojure macro without using eval? The only thing I could come up with so far is this:
(def ^{:a :b} my-var)
(defmacro my-macro [s] (prn (eval `(meta (var ~s)))))
(my-macro my-var)
;; Prints {:a :b, :name my-var, ...}
I ended up finding a solution:
(def ^{:a :b} my-var)
(defmacro my-macro [s] (prn (meta (resolve s))))
(my-macro my-var)
;; Prints {:a :b, :name my-var, ...}
So the key part here is to use resolve function to get the var associated to the symbol.
I'm generating json as literally as I can in clojure. My problem is that certain branches of the json are only present if given parameters are given. Here is a sample of such a condition
(defn message-for
[name uuid & [generated-uuids]]
{:message {:id (generate-uuid)
:details {:name name}
:metadata {:batch (merge {:id uuid}
(when generated-uuids (let [batches (map #(array-map :id %) generated-uuids)]
{:generatedBatches batches})))}}})
Unfortunately the when/let part is quite ugly. This same could be achieved using when-let as following but it doesn't work because my map returns [] instead of a nil.
(defn message-for
[name uuid & [generated-uuids]]
{:message {:id (generate-uuid)
:details {:name name}
:metadata {:batch (merge {:id uuid}
(when-let [batches (map #(array-map :id %) generated-uuids)]
{:generatedBatches batches}))}}})
Any ideas if I could somehow make when-let consider an empty list/array/seq as false so I could clean up my code a bit?
not-empty returns its argument if it is not empty.
When using when-let with a collection, always use not-empty
to retain the collection type
make refactoring easier
expressivenes
(when-let [batches (not-empty (map ...))]
...)
In your case I'd however prefer something like this:
...
:metadata {:batch (cond-> {:id uuid}
(seq generated-uuids)
(assoc :generatedBatches (map ...)))}
...
Notice that all three of the advantages listed above where met, without a nested let.
Also notice a new advantage
easier to extend with more conditions lateron
seq returns nil on an empty input sequence so you could do:
(when-let [batches (seq (map #(array-map :id %) generated-uuids))]
{:generatedBatches batches}))}}})
I'm trying to handle following DSL:
(simple-query
(is :category "car/audi/80")
(is :price 15000))
that went quite smooth, so I added one more thing - options passed to the query:
(simple-query {:page 1 :limit 100}
(is :category "car/audi/80")
(is :price 15000))
and now I have a problem how to handle this case in most civilized way. as you can see simple-query may get hash-map as a first element (followed by long list of criteria) or may have no hash-mapped options at all. moreover, I would like to have defaults as a default set of options in case when some (or all) of them are not provided explicite in query.
this is what I figured out:
(def ^{:dynamic true} *defaults* {:page 1
:limit 50})
(defn simple-query [& body]
(let [opts (first body)
[params criteria] (if (map? opts)
[(merge *defaults* opts) (rest body)]
[*defaults* body])]
(execute-query params criteria)))
I feel it's kind of messy. any idea how to simplify this construction?
To solve this problem in my own code, I have a handy function I'd like you to meet... take-when.
user> (defn take-when [pred [x & more :as fail]]
(if (pred x) [x more] [nil fail]))
#'user/take-when
user> (take-when map? [{:foo :bar} 1 2 3])
[{:foo :bar} (1 2 3)]
user> (take-when map? [1 2 3])
[nil [1 2 3]]
So we can use this to implement a parser for your optional map first argument...
user> (defn maybe-first-map [& args]
(let [defaults {:foo :bar}
[maybe-map args] (take-when map? args)
options (merge defaults maybe-map)]
... ;; do work
))
So as far as I'm concerned, your proposed solution is more or less spot on, I would just clean it up by factoring out parser for grabbing the options map (here into my take-when helper) and by factoring out the merging of defaults into its own binding statement.
As a general matter, using a dynamic var for storing configurations is an antipattern due to potential missbehavior when evaluated lazily.
What about something like this?
(defn simple-query
[& body]
(if (map? (first body))
(execute-query (merge *defaults* (first body)) (rest body))
(execute-query *defaults* body)))