Why is there a difference in return types of assoc and dissoc in Clojure, when their argument is a record? I mean that assoc'ing a non-existent key still returns a record, but dissoc'ing an existing key returns a map.
But, in a sense, both should produce either a map or a record, but not exhibit different behavior. What is the reason for this dissimilarity?
Record will be converted to an ordinary clojure map only if you'll dissoc one of its predefined fields. It's very reasonable behavior, because records can't have undefined fields.
Consider the following code:
(defrecord Point [x y])
(def p (Point. 1 2)) ; => Point{:x 1, :y 2}
(assoc p :x 3) ; => Point{:x 3, :y 2}
(dissoc p :x) ; => {:y 2}
(assoc p :z 3) ; => Point{:x 1, :y 2, :z 3}
(dissoc p :z) ; => Point{:x 1, :y 2}
(-> p
(assoc :z 3) ; => Point{:x 1, :y 2, :z 3}
(dissoc :z)) ; => Point{:x 1, :y 2}
As you can see, both assoc and dissoc return a record as long as it satisfies Point definition.
Record instances are guaranteed to include all the fields declared in the record definition.
When a declared field is removed from an instance, this guarantee would be violated. Hence a map is returned.
Apparently they are not guaranteed to exclude all fields not declared in the record definition, thus new fields can be added to instances.
Related
This makes sense:
user=> (into {} [[:a 1] [:b 2]])
{:a 1, :b 2}
But why does this generate an error?
user=> (into {} (partition 2 [:a 1 :b 2]))
ClassCastException clojure.lang.Keyword cannot be cast to java.util.Map$Entry clojure.lang.ATransientMap.conj (ATransientMap.java:44)
Just to be sure:
user=> (partition 2 [:a 1 :b 2])
((:a 1) (:b 2))
Does into have a problem with lazy sequences? If so, why?
Beyond an explanation of why this doesn't work, what is the recommended way to conj a sequence of key-value pairs like [:a 1 :b 2] into a map? (apply conj doesn't seem to work, either.)
You can apply the sequence to assoc:
(apply assoc {:foo 1} [:a 1 :b 2])
=> {:foo 1, :a 1, :b 2}
Does into have a problem with lazy sequences? If so, why?
No, into is commonly used with lazily evaluated sequences. This is lazy, but each key/value tuple is a vector, which is why it works when into is reducing the pairs into the map:
(into {} (map vector (range 3) (repeat :x)))
=> {0 :x, 1 :x, 2 :x}
This doesn't work because the key/value pairs are lists:
(into {} (map list (range 3) (repeat :x)))
So the difference isn't laziness; it's due to into using reduce using conj on the map, which only works with vector key/value pairs (or MapEntrys):
(conj {} [:a 1]) ;; ok
(conj {} (MapEntry. :a 1)) ;; ok
(conj {} '(:a 1)) ;; not ok
Update: assoc wrapper for applying empty/nil sequences as suggested in comments:
(defn assoc*
([m] m)
([m k v & kvs]
(apply assoc m k v kvs)))
The recommended way – (assuming the seq arg is non-empty, as pointed out by the OP) – would be
Clojure 1.9.0
user=> (apply assoc {} [:a 1 :b 2])
{:a 1, :b 2}
The version with partition doesn't work because the blocks that partition returns are seqs and those are not treated as map entries when conj'd on to a map the way vectors and actual map entries are.
E.g. (into {} (map vec) (partition 2 [:a 1 :b 2])) would work because here the pairs get converted to vectors before conjing.
Still the approach with assoc is preferable unless there's some particular circumstance that makes into convenient (like, say, if you have a bunch of transducers that you want to use for preprocessing your partition-generated pairs etc.).
Clojure treats a 2-vec such as [:a 1] as equivalent to a MapEntry, doing what amounts to "automatic type conversion". I try to avoid this and always be explicit.
(first {:a 1}) => <#clojure.lang.MapEntry [:a 1]>
(conj {:a 1} [:b 2]) => <#clojure.lang.PersistentArrayMap {:a 1, :b 2}>
So we see that a MapEntry prints like a vector but has a different type (just like a Clojure seq prints like a list but has a different type). seq converts a Clojure map into a sequence of MapEntry's, and first gets us the first one (most Clojure functions call (seq ...) on any input collections before any other processing).
Notice that conj does the inverse type conversion, treating the vector [:b 2] as if it were a MapEntry. However, conj won't perform automatic type conversion for a list or a seq:
(throws? (conj {:a 1} '(:b 2)))
(throws? (into {:a 1} '(:b 2)))
into has the same problem since it is basically just (reduce conj <1st-arg> <2nd-seq>).
The other answers already have 3 ways that work:
(assoc {} :b 2) => {:b 2}
(conj {} [:b 2]) => {:b 2}
(into {} [[:a 1] [:b 2]]) => {:a 1, :b 2}
However, I would avoid those and stick to either hash-map or sorted-map, both of which avoid the problem of empty input seqs:
(apply hash-map []) => {} ; works for empty input seq
(apply hash-map [:a 1 :b 2]) => {:b 2, :a 1}
If your input sequence is a list of pairs, flatten is sometimes helpful:
(apply sorted-map (flatten [[:a 1] [:b 2]])) => {:a 1, :b 2}
(apply hash-map (flatten '((:a 1) (:b 2)))) => {:a 1, :b 2}
P.S.
Please be note that these are not the same:
java.util.Map$Entry (listed in jdk docs as "Map.Entry")
clojure.lang.MapEntry
P.P.S
If you already have a map and want to merge in a (possibly empty) sequence of key-value pairs, just use a combination of into and hash-map:
(into {:a 1} (apply hash-map [])) => {:a 1}
(into {:a 1} (apply hash-map [:b 2])) => {:a 1, :b 2}
I have a data set that looks like this:
({"1880" 5} {"1951" 6} {"1952" 5} {"1976" 10} {"1902" 7} {"1919" 7} {"1949" 12} {"1814" 4} {"1930" 11})
I am trying to get the key with the highest value. So in the case above I want to get the value "1949" back. I believe my answer lies with max-key, however I don't fully understand how max-key works. For clarity as one answer was about looking at the string value:
I want the string "1949" as the result because it has the highest number associated with it of 12
Just use max-key, with a function to grab the val from each map:
(def data
[{"1880" 5} {"1951" 6} {"1952" 5} {"1976" 10} {"1902" 7} {"1919" 7} {"1949" 12} {"1814" 4} {"1930" 11}])
(apply max-key #(val (first %)) data) => {"1949" 12}
You need the first function to convert each single element map into a MapEntry. You can then use the val function to grab value out of the MapEntry:
(first {"1880" 5}) => <#clojure.lang.MapEntry ["1880" 5]>
(val (first {"1880" 5})) => <#java.lang.Long 5>
Be sure to bookmark The Clojure CheatSheet and peruse it often!
P.S. Why first works for this:
Note that you can convert a map into sequence of MapEntry's using either seq or vec:
some-map => <#clojure.lang.PersistentArrayMap {:a 1, :b 2}>
(seq some-map) => <#clojure.lang.PersistentArrayMap$Seq ([:a 1] [:b 2])>
(vec some-map) => <#clojure.lang.PersistentVector [[:a 1] [:b 2]]>
You then need the first item from this seq/vector, which is where first comes in:
(first (vec some-map)) => <#clojure.lang.MapEntry [:a 1]>
Note, however, that first implicitly calls seq on whatever you pass to it, so we can skip the conversion and let first implicitly convert the map into a seq of MapEntry's for us:
(first some-map) => <#clojure.lang.MapEntry [:a 1]>
You can sort your list of maps by the value of each map within it.
(last (sort-by (comp second first) data))
=> {"1949" 12}
But looking at the data, I'm wondering it wouldn't just be a single map rather than a sequence of maps. So I'm going to make the assumption that your data will never have duplicate keys, and then we can work with just a single map structure and it's easier:
(into {} data)
=> {"1919" 7, "1880" 5, "1814" 4, "1902" 7, "1951" 6, "1949" 12, "1976" 10, "1930" 11, "1952" 5}
Then you can get the same answer like this:
(last (sort-by second (into {} data)))
=> ["1949" 12]
You can call first with these outputs to get just the string "1949".
Here's another way to do it, sorting descending with a custom/reversed comparator:
(->> (into {} data)
(sort-by second #(compare %2 %1))
(ffirst))
=> "1949"
Since your keys aren't numbers (they are strings) you can't use max-key without casting to a number.
You could achieve your desired result with this:
(last (sort (mapcat keys ({"1889" 1} {"1990" 2}))))
I'm just starting to learn clojure and have been reading some simple examples and then doing my best to rtfm for concepts.
However I'm a bit confused by what val is doing in the example below. This has been taken from the Clojure doc examples for val.
(first {:one :two}) ;; => [:one :two]
Here, a hash-map with a key of :one and a value of :two is being passed to first. Behind the scenes, Clojure converts this hash-map to a sequence of vectors. Since there is only one vector in this sequence, it returns [:one :two].
(val (first {:one :two})) ;; => :two
(val [:one :two]) ;; => ClassCastException clojure.lang.PersistentVector cannot be cast to java.util.Map$Entry
(val {:one :two}) ;; => ClassCastException clojure.lang.PersistentArrayMap cannot be cast to java.util.Map$Entry
If I try to call val on a (I think) a hash-map (I realize it's actually a "persistent array map"), I get the exception as seen above.
I'm also confused by the following:
(first {:one :two}) ;; # => [:one :two] (this is a vector right?)
(val [:one :two]) ;; # => ClassCastException (why doesn't this give back the same result as the example above?)
Why can't I just plug the result of (first {:one :two}) into val and get the same result?
Additionally, another example listed on the page is the following:
(map val {:a 1 :b 2}) ;; => (1 2)
Here's how I read the line. Take the array-map {:a 1 :b 2}. For each key-value pair, call val on the pair to return the value. Return a sequence from the resulting calls to map. Is this the correct way to read the problem?
As always, thanks for any and all help.
a sequence of a map produces MapEntry values as you've noted, which look like and can be compared with vectors
user=> (= (first {:a 1 :b 2}) [:a 1])
true
but aren't the same class
user=> (= (class (first {:a 1 :b 2})) (class [:a 1]))
false
So although the output on the repl of (first {:a 1}) looks like a vector, it isn't, it's a MapEntry, so it can be passed to val, but the vector [:a 1] cannot, hence the class cast exception.
Your reading of what map is doing is correct at a high level, a little more specific might be "For each entry in the sequence from {:a 1 :b 2} (which are MapEntry values) call the function val on each item (a MapEntry), and generate a sequence from the results".
This will explain why something like
user=> (map val '([:a 1] [:b 2]))
will cause the same ClassCastExceptions as the sequence generates Vector elements, not MapEntry elements.
val returns value of a map entry, not a map.
(first {:one :two}) return the first map entry (although it appears to be just a vec)
(map val {:one :two}) return the value of every entry, and is equivalent to (vals {:one :two})
(first {:one :two}) ;; # => [:one :two] (this is a vector right? No, it's not.)
[:one :two] in this case is a MapEntry, not a vector.
I have a bunch of records (A B C) that implement a protocol P.
I want to write a method which will select one of the types of records, construct it, and then call a method on it.
For example, if I have a list of the records
(def types '(A B C)) I want to do something like (->(first types) 1 2 3)
Well, functions are also values and can be stored in and retrieved from data structures. So you should be able to just store the constructor functions you want to pick from in some convenient format and use them from there. Something like:
(defrecord foo [x y])
(defrecord bar [x y z])
(def constructors [->foo ->bar])
((first constructors) 4 5) ;;=> #user.foo{:x 4, :y 5}
;;or
(apply (second constructors) [20 true :baz]) ;;=> #user.bar{:x 20, :y true, :z :baz}
(-> (second constructors) (apply '(59 -30 false))) ;;=> #user.bar{:x 59, :y -30, :z false}
or you can even skip the data structure entirely:
(defn quxx [n a-map]
((if (< 25 n) map->foo map->bar) a-map))
(quxx 2 {:x 3 :y 9 :z -200}) ;;=> #user.bar{:x 3, :y 9, :z -200}
(quxx 29 {:x 3 :y 9 :z -200}) ;;=> #user.foo{:x 3, :y 9, :z -200}
Given that I have two objects defined (in this case records):
(defrecord rec1 [one two])
(defrecord rec2 [one two])
and I have these two records in (uninstanciated) in a list:
(def recs [rec1 rec2])
How is the best way to instanciate these two records in the list?
The following gives an exception
(map (fn [rec] (rec. 1 2)) recs)
because new and the dot constructor expects a classname-symbol. So what is the best way to go about this? So far reflection seems to be the only way:
(map #(clojure.lang.Reflector/invokeConstructor % (to-array [1 2 3])) recs)
This just seems like an overly ugly (and slow) way to perform the seemingly simple task of calling the constructor on an object. So what is the correct (or idiomatic) way?
Your question isn't clear to me, but here are some observations:
rec1 and rec2 are not different ctors of the same type, rather separate types each with 1 ctor (co-incidentally the two ctor have the same arity/ signature)
your 'uninstatiated list' doesn't make sense.
defrecord generates some helper functions for just this use...
I'm not sure exactly what you're trying to do, here are some examples that might clarify your requirements...
(def r1 (->rec1 1 2))
;=> #user.rec1{:one 1, :two 2}
(def c1 (rec1. 1 2))
;=> #user.rec1{:one 1, :two 2}
(def m1 (map->rec1 {:one 1 :two 2}))
;=> #user.rec1{:one 1, :two 2}
(def r2 (->rec2 1 2))
;=> #user.rec2{:one 1, :two 2}
(def c2 (rec2. 1 2))
;=> #user.rec2{:one 1, :two 2}
(def recs [(rec1. 1 2) (->rec1 1 2)])
(for [[n m] (partition 2 (range 4))] (->rec1 n m))
;=> (#user.rec1{:one 0, :two 1} #user.rec1{:one 2, :two 3})