I'm very new to Clojure and learning Clojure by reading
good open source code. So I choose Ring and starting to read
the code but got stuck in assoc-query-params function.
(which is located in ring.middleware/params.clj)
And I could not understand why "merge-with" is used.
Can anybody help me to understand this code snippet?
(defn- assoc-query-params
"Parse and assoc parameters from the query string with the request."
[request encoding]
; I think (merge request (some-form)) is enough
; but the author used merge-with with merge function.
(merge-with merge request
(if-let [query-string (:query-string request)]
(let [params (parse-params query-string encoding)]
{:query-params params, :params params})
{:query-params {}, :params {}})))
Here's the description of the merge function: reworded it says that if a key is met more than once than value in the latest map will be selected. In the example that you posted that would mean that values of :query-params :params will be taken as is from the tail of the function instead of combining them with what's in the request.
Let's look at the example:
(def m {:a {:a-key1 "value1"} :b {:b-key1 "value3"} :c {}})
(def m2 {:a {:a-key2 "value2"} :b {}})
(merge m m2)
;-> {:a {:a-key2 "value2"}, :b {}, :c {}}
(merge-with merge m m2)
;-> {:a {:a-key1 "value1", :a-key2 "value2"}, :b {:b-key1 "value3"} :c {}}
So (merge-with merge ...) construct gives us a way to merge maps in the map. You can look at it this way: merge-with will group all key/value pairs by the key (:a :b :c in our example) and apply merge to their values.
{:a (merge {:a-key1 "value1"} {:a-key2 "value2"})
:b (merge {:b-key1 "value3"} {})
:c (merge {})}
Having handled that I think that the original intention of the assoc-query-params author is to extend :query-params and :params instead of completely replacing them.
Related
I'm trying to coerce a map using prismatic-schema (1.0.4)
I'm trying to coerce
{:a 1}
to
{:b 1}
Using a custom matcher with the schema:
{:b s/Int}
But this code isn't working:
(require '[schema.core :as s])
(require '[schema.coerce :as coerce])
((coerce/coercer {:b s/Int}
(fn [s]
(when (= s s/Keyword)
(fn [x]
(if (= x :a)
:b
x)))))
{:a 1})
Output:
#schema.utils.ErrorContainer{:error {:b missing-required-key, :a disallowed-key}}
I tried debugging it by running the following code which matches everything in the schema and outputs the current value and schema being matched:
((coerce/coercer {:b s/Int}
(fn [s]
(when true
(fn [x]
(println s x)
x))))
{:a 1})
Output:
{:b Int} {:a 1}
=>
#schema.utils.ErrorContainer{:error {:b missing-required-key, :a disallowed-key}}
It looks as though the matcher bombs out as soon as it gets to the map?
Schema first breaks your map up into pieces that match up to the schema, then coerces each MapEntry to the corresponding MapEntry schema, and so on down. This breakdown fails in your case, so you never get to the key.
To accomplish what you want, you'll have to attach the coercion to the map schema and use e.g. clojure.set/rename-keys in your coercion function:
(def Foo {:b s/Int})
((coerce/coercer
Foo
(fn [s]
(when (= s Foo)
#(clojure.set/rename-keys % {:a :b}))))
{:a 1})
Is there a way to describe arbitrary lazy self-recursive data structures in Clojure?
Let's say for example I wanted to do something like this:
(def inf-seq (fn rec [] (lazy-seq (cons 42 (rec)))))
(take 3 (inf-seq))
but with a map:
(def inf-map (fn rec [] (??? {:a (rec) :b 42})))
(get-in (inf-map) [:a :a :a :b])
Sequence laziness does not apply to deferred function evaluation in Clojure, which you would obviously need for constructing infinitely nested maps.
Try using Delays:
user=> (def inf-map (fn rec [] {:a (delay (rec)) :b 42}))
#'user/inf-map
user=> (inf-map)
{:a #<Delay#4e9f9a19: :pending>, :b 42}
user=> #(:a (inf-map))
{:a #<Delay#5afd479c: :pending>, :b 42}
I have this function:
(defn dissoc-all [m kv]
(let [[k & ks] kv]
(dissoc m k ks)))
Where m is the map and kv is the vector of keys. I use it like this:
(dissoc-all {:a 1 :b 2} [:a :b])
=>{:b 2}
This is not what I've expected. ks has :b but I don't know why it is not being use by dissoc. Anyone can help me with this?
Edit: Added question is that why is this not triggering the 3rd overload of dissoc, which is dissoc [map key & ks]?
Changed name from dissoc-in to dissoc-all as noisesmith have said, -in is not a proper name for this and I agree.
This won't work because ks is a collection of all the elements in kv after the first. So instead of :b it is [:b].
Instead, you can just use apply:
(defn dissoc-in [m vs]
(apply dissoc m vs))
Also, dissoc-in is an odd name for this function, because the standard functions with -in in the name all do nested access, and this does not use the keys to do any nested access of the map.
Why not something like this?
(defn dissoc-all [m ks]
(apply dissoc m ks))
(dissoc-all {:a 1 :b 2} [:a :b])
=> {}
The reason the third overlod of dissoc is not getting called is because it does not expect a collection of keys like [:a :b] - it expects just the keys.
For example:
(dissoc {:a "a" :b "b" :c "c" :d "d"} :a :b :c)
=> {:d "d"}
Further to noisesmith's answer:
You're being confused by the overloads/arities of dissoc, which have this simple effect:
[m & ks]
"Returns a new map of the same (hashed/sorted) type,
that does not contain a mapping for any of ks. "
The explicit arities for no keys and one key are for performance. Many clojure functions are so organised, and the documentation follows the organisation, not the underlying idea.
Now, the action of
(dissoc-all {:a 1 :b 2} [:a :b])
;{:b 2}
is to bind
k to :a
ks to [:b]
Note the latter. The example removes the :a but fails to remove the [:b], which isn't there.
You can use apply to crack open ks:
(defn dissoc-all [m kk]
(let [[k & ks] kk]
(apply dissoc m k ks)))
(dissoc-all {:a 1 :b 2} [:a :b])
;{}
... or, better, do as #noisesmith does and short-circuit the destructuring, using apply at once.
I have the following functions and reduced sample:
(defn parse-time
[time-str]
(->> time-str
(re-find #"(\d{1,2}):(\d{2}):(\d{2})")
...))
(defn coerce-times
[m & ks]
(update-in m ks parse-time))
(coerce-times {:depart "05:05:00" :arrive "05:05:00"} :depart :arrive)
This works as expected with only one key, but when I try to use multiple keys (as in the example above), I get a NPE. Line 20 is the re-find line.:
java.lang.NullPointerException: null
at java.util.regex.Matcher.getTextLength (Matcher.java:1234)
java.util.regex.Matcher.reset (Matcher.java:308)
java.util.regex.Matcher.<init> (Matcher.java:228)
java.util.regex.Pattern.matcher (Pattern.java:1088)
clojure.core$re_matcher.invoke (core.clj:4460)
clojure.core$re_find.invoke (core.clj:4512)
tempest.core$parse_time.invoke (core.clj:20)
...
Can someone please help me understand what I'm doing wrong and how I can fix this?
The keys vector provided to update-in is not a collection of keys to operate on, but a series of lookups to follow:
user> (update-in {:a {:b {:c 0}}} [:a :b :c] inc)
{:a {:b {:c 1}}}
I have an atom that has two parts to it.
(def thing (atom {:queue '() :map {}}))
I want to update both :queue and :map in one atomic stroke, to prevent them from getting off-sync.
Queue individually:
(swap! thing update-in [:queue] (list 1))
(From this question: How to append to a nested list in a Clojure atom?)
Map individually:
(swap! thing assoc-in [:map 1] (:key :value))
(From this question: Using swap to MERGE (append to) a nested map in a Clojure atom?)
How can I do these both within a single swap statement? (assuming that would prevent them from getting off-sync?)
You have one change you want to make, right? And you could write that change as a pure function? All you need to do is write that function, and pass it as the argument to swap!.
(defn take-from-queue [{q :queue, m :map}]
{:queue (rest q), :map (assoc m :new-task (first q))})
(swap! thing take-from-queue)
Where of course I have no idea what you actually want the body of your function to do, so I've made up something that doesn't throw an exception.
Say you have a hash-map atom:
(def m1 (atom {:a "A" :b "B"}))
To change :a and :b at the same time, changing their values to values that are different, say the numbers 1 and 2, use this function:
(defn my-swap! [params]
(swap! m1 (fn [old new] new) params))
, like so:
(my-swap! {:a 1 :b 2}) ;=> {:a 1, :b 2}
And the same effect could be achieved with the following function and execution:
(defn my-multi-swap! [params1 params2]
(swap! m1 (fn [old new1 new2] new2) params1 params2))
(my-multi-swap! {} {:a 1 :b 2}) ;=> {:a 1, :b 2}
Normally reset! is used if you want to ignore the old value. Here we use it:
(defn my-merge-swap! [params]
(swap! m1 (fn [old new] (merge old new)) params))
(my-merge-swap! {:b 3}) ;=> {:a "A", :b 3}
The first parameter to the swap! function is the existing value of the atom, and you must pass in one or more extra parameters, which you can use to give the atom its new value.