So I'm used to having a nested array or map of settings in my applications. I tried setting one up in Clojure like this:
(def gridSettings
{:width 50
:height 50
:ground {:variations 25}
:water {:variations 25}
})
And I wondered if you know of a good way of retrieving a nested value? I tried writing
(:variations (:ground gridSettings))
Which works, but it's backwords and rather cumbersome, especially if I add a few levels.
That's what get-in does:
(get-in gridSettings [:ground :variations])
From the docstring:
clojure.core/get-in
([m ks] [m ks not-found])
Returns the value in a nested associative structure,
where ks is a sequence of keys. Returns nil if the key
is not present, or the not-found value if supplied.
You can use the thread-first macro:
(-> gridSettings :ground :variations)
I prefer -> over get-in except for two special cases:
When the keys are an arbitrary sequence determined at runtime.
When supplying a not-found value is useful.
Apart from what other answers has mentioned (get-in and -> macro), sometimes you want to fetch multiple values from a map (nested or not), in those cases de-structuring can be really helpful
(let [{{gv :variations} :ground
{wv :variations} :water} gridSettings]
[gv wv])
Maps are partial functions (as in not total). Thus, one can simply apply them as functions. Based on the map from the question:
(gridSettings :ground)
;=> {:variations 25}
The result is a map. So, it can be applied again, which results in a very similar (but not backwards) "syntax" as proposed in the question:
((gridSettings :ground) :variations)
;=>25
Related
Here's a use of the standard 'contains?' function in Clojure-
(contains? {:state "active", :course_n "law", :course_i "C0"} :state)
and it returns the expected
true
I used the following
Clojure: Idiomatic way to call contains? on a lazy sequence
as a guide for building a lazy-contains? as this is what I need for my present use-case.
The problem I'm facing is that for a map these alternatives are not returning the same answer, giving either a false or a nil response. I've tried looking at the source for contains? and it's slow going trying to understand what's happening so I can correct the lazy-contains? appropriately (for the record Clojure is essentially my first programming language, and my exposure to Java is very limited).
Any thoughts or ideas on how I might approach this? I tried every variant on the linked question I could.
Thanks in advance.
Edited to remove the error pointed out by #amalloy.
I think your problem is with the way that maps present themselves as sequences.
Given
(def data {:state "active", :course_n "law", :course_i "C0"})
then
(seq data)
;([:state "active"] [:course_i "C0"] [:course_n "law"])
... a sequence of key-value pairs.
So if we define (following #chouser)
(defn lazy-contains? [coll x]
(some #(= x %) coll))
... then
(lazy-contains? data :state)
;nil
... a false result, whereas ...
(lazy-contains? data [:state "active"])
;true
This is what #Ankur was getting at, showing you a function treating a map as a sequence consistent with contains? on the map itself.
The standard contains? works with keyed/indexed collections - maps
or sets or vectors - where it tests for the presence of a key.
Our lazy-contains? works with anything sequable, including all the
standard collections, testing for the presence of a value.
Given the way that keyed/indexed collections present as sequences, these are bound to be inconsistent.
You can try the below implementation (for maps only):
(defn lazy-contains? [col key]
(some (fn [[k v]] (= k key)) col))
Remember, contains? is to check the existence of a key in a collection, in maps the key is obvious, in other supported collections (like vector) the key is the index.
A "lazy" implementation of contains? is undesirable where checking for presence
of a key in a hash-map or of a value in a set
(contains? #{:foo} :foo}) => true
(contains? {:foo :bar} :foo) => true
of an index of a vector array or string.
(contains? [:foo] 0) => true
(contains? (int-array 7) 6) => true
(contains? "foo" 2) => true
Quoting from the contains? docstring:
'contains?' operates constant or logarithmic time; it will not
perform a linear search for a value.
some is a tool for linear searching. When searching for an element in a set or vector, it can take the input sequence length times as long as contains? or longer in the worst case and will take more time than contains? in almost every case.
contains? can't be implemented "lazy" as it does not produce a sequence. However, some stops consuming a lazy sequence as soon as it has determined a return value.
(some zero? (range))
;; true
Notice that maps and sets are never sequential or lazy.
I am learning Clojure, and I saw this bit of code online:
(count (filter #{42} coll))
And it does, as stated, count occurrences of the number 42 in coll. Is #{42} a function? The Clojure documentation on filter says that it should be, since the snippet works as advertised. I just have no idea how it works. If someone could clarify this for me, that would be great. My own solution to this same thing would have been:
(count (filter #(= %1 42) coll))
How come my filtering function has parenthesis and the snippet I found online has curly braces around the filtering function (#(...) vs. #{...})?
=> #{42}
#{42}
Defines a set...
=> (type #{42})
clojure.lang.PersistentHashSet
=> (supers (type #{42}))
#{clojure.lang.IHashEq java.lang.Object clojure.lang.IFn ...}
Interestingly the set implements IFn so you can treat it like a function. The behaviour of the function is "if this item exists in the set, return it".
=> (#{2 3} 3)
3
=> (#{2 3} 4)
nil
Other collections such as map and vector stand in as functions in a similar fashion, retrieving by key or index as appropriate.
=> ({:x 23 :y 26} :y)
26
=> ([5 7 9] 1)
7
Sweet, no? :-)
Yes, #{42} is a function,
because it's a set, and sets, amongst other capabilities, are
functions: they implement the clojure.lang.IFn interface.
Applied to any value in the set, they return it; applied to anything
else, they return nil.
So #{42} tests whether its argument is 42 (only nil and false are false, remember).
The Clojure way is to make everything a function that might usefully be one:
Sets work as a test for membership.
Maps work as key lookup.
Vectors work as index lookup.
Keywords work as lookup in the map argument.
This
often saves you a get,
allows you, as in the question, to pass naked data structures to higher order functions
such as filter and map, and
in the case of keywords, allows you to move transparently between maps and records
for holding your data.
In clojure,
(assoc {})
throws an arity exception, but
(dissoc {})
does not. Why? I would have expected either both of them to throw an exception, or both to make no changes when no keys or values are provided.
EDIT: I see a rationale for allowing these forms; it means we can apply assoc or dissoc to a possibly empty list of arguments. I just don't see why one would be allowed and the other not, and I'm curious as to whether there's a good reason for this that I'm missing.
I personally think the lack of 1-arity assoc is an oversight: whenever a trailing list of parameters is expected (& stuff), the function should normally be capable of working with zero parameters in order to make it possible to apply it to an empty list.
Clojure has plenty of other functions that work correctly with zero arguments, e.g. + and merge.
On the other hand, Clojure has other functions that don't accept zero trailing parameters, e.g. conj.
So the Clojure API is a bit inconsistent in this regard.....
This is not an authoritative answer, but is based on my testing and looking at ClojureDocs:
dissoc 's arity includes your being able to pass in one argument, a map. No key/value is removed from the map, in that case.
(def test-map {:account-no 12345678 :lname "Jones" :fnam "Fred"})
(dissoc test-map)
{:account-no 12345678, :lname "Jones", :fnam "Fred"}
assoc has no similar arity. That is calling assoc requires a map, key, and value.
Now why this was designed this way is a different matter, and if you do not receive an answer with that information -- I hope you do -- then I suggest offering a bounty or go on Clojure's Google Groups and ask that question.
Here is the source.
(defn dissoc
"dissoc[iate]. Returns a new map of the same (hashed/sorted) type,
that does not contain a mapping for key(s)."
{:added "1.0"
:static true}
([map] map)
([map key]
(. clojure.lang.RT (dissoc map key)))
([map key & ks]
(let [ret (dissoc map key)]
(if ks
(recur ret (first ks) (next ks))
ret))))
I have a defrecord called a bag. It behaves like a list of item to count. This is sometimes called a frequency or a census. I want to be able to do the following
(def b (bag/create [:k 1 :k2 3])
(keys bag)
=> (:k :k1)
I tried the following:
(defrecord MapBag [state]
Bag
(put-n [self item n]
(let [new-n (+ n (count self item))]
(MapBag. (assoc state item new-n))))
;... some stuff
java.util.Map
(getKeys [self] (keys state)) ;TODO TEST
Object
(toString [self]
(str ("Bag: " (:state self)))))
When I try to require it in a repl I get:
java.lang.ClassFormatError: Duplicate interface name in class file compile__stub/techne/bag/MapBag (bag.clj:12)
What is going on? How do I get a keys function on my bag? Also am I going about this the correct way by assuming clojure's keys function eventually calls getKeys on the map that is its argument?
Defrecord automatically makes sure that any record it defines participates in the ipersistentmap interface. So you can call keys on it without doing anything.
So you can define a record, and instantiate and call keys like this:
user> (defrecord rec [k1 k2])
user.rec
user> (def a-rec (rec. 1 2))
#'user/a-rec
user> (keys a-rec)
(:k1 :k2)
Your error message indicates that one of your declarations is duplicating an interface that defrecord gives you for free. I think it might actually be both.
Is there some reason why you cant just use a plain vanilla map for your purposes? With clojure, you often want to use plain vanilla data structures when you can.
Edit: if for whatever reason you don't want the ipersistentmap included, look into deftype.
Rob's answer is of course correct; I'm posting this one in response to the OP's comment on it -- perhaps it might be helpful in implementing the required functionality with deftype.
I have once written an implementation of a "default map" for Clojure, which acts just like a regular map except it returns a fixed default value when asked about a key not present inside it. The code is in this Gist.
I'm not sure if it will suit your use case directly, although you can use it to do things like
user> (:earth (assoc (DefaultMap. 0 {}) :earth 8000000000))
8000000000
user> (:mars (assoc (DefaultMap. 0 {}) :earth 8000000000))
0
More importantly, it should give you an idea of what's involved in writing this sort of thing with deftype.
Then again, it's based on clojure.core/emit-defrecord, so you might look at that part of Clojure's sources instead... It's doing a lot of things which you won't have to (because it's a function for preparing macro expansions -- there's lots of syntax-quoting and the like inside it which you have to strip away from it to use the code directly), but it is certainly the highest quality source of information possible. Here's a direct link to that point in the source for the 1.2.0 release of Clojure.
Update:
One more thing I realised might be important. If you rely on a special map-like type for implementing this sort of thing, the client might merge it into a regular map and lose the "defaulting" functionality (and indeed any other special functionality) in the process. As long as the "map-likeness" illusion maintained by your type is complete enough for it to be used as a regular map, passed to Clojure's standard function etc., I think there might not be a way around that.
So, at some level the client will probably have to know that there's some "magic" involved; if they get correct answers to queries like (:mars {...}) (with no :mars in the {...}), they'll have to remember not to merge this into a regular map (merge-ing the other way around would work fine).
I'm looking for an easy and safe way to parse a map, and only a map, from a string supplied by an untrusted source. The map contains keywords and numbers. What are the security concerns of using read to do this?
read is by default totally unsafe, it allows arbitrary code execution. Try (read-string "#=(println \"hello\")") as an example.
You can make it safer by binding *read-eval* to false. This will cause an exception to be triggered if there #= notation is used. For example:
(binding [*read-eval* false] (read-string "#=(println \"hello\")"))
Finally, depending on how you are using it there is a potential denial of service attack by supplying a large number of keywords (:foo, :bar). Keywords are interned and never freed so if enough are used the process will run out of memory. There's some discussion about that on the clojure-dev list.
If you want to be safe I think you basically need to parse it by hand without doing an eval. Here is an example of one way to do it:
(apply hash-map
(map #(%1 %2)
(cycle [#(keyword (apply str (drop 1 %)))
#(Integer/parseInt %)])
(string/split ":a 23 :b 32 :c 32" #" ")))
Depending on the types of numbers you want to support and how much error checking you want to do you will want to modify the two functions that are being cycled over to process every map every other value to a keyword or number.