3 ms·
There are a few things I would like to see added as I have found them very useful in day to day work with Clojure(Script). cond-> and cond->> which takes an
by jetti 5y ago
There are a few things I would like to see added as I have found them very useful in day to day work with Clojure(Script).
cond-> and cond->> which takes an expression and then threads it through test/form pairs without short circuiting by stopping on the first test that returns true. I have used this a lot when conditionally adding to maps. Something like this:
(let [base-map {:id "123"}
name (function-that-could-return-nil)
address (another-function-that-could-return-nil)]
(cond-> base-map
(some? name) (assoc :name name)
(some? address) (assoc :address address)))
If name and address are nil then I will still have the base-map.
I also use some-> and some->> a lot as well. some-> and some->> operate similar to -> and ->> respectively but the difference is that there is a nil check before subsequent form. It will prevent nil results to keep being passed and can help alleviate NullPointerExceptions.
As for maps, I recommend adding assoc-in, dissoc, update and update-in. assoc-in allows adding nesting to a map. I could do (assoc-in {} [:base :value] "test") which would result in a map that looks like {:base {:value "test"}}. dissoc removes a key from a map. update allows you to "update" a map by passing a key and function where the function has an argument that is the old argument. An example of that is (update {:name "James" :age 26} :age inc) which will increase the value of :age by 1. update-in allows you to do the same as update but in a nested structure
- fulafel 5y agoI see this kind of map update code usually written using merge, like this (ignoring DRY itch for now) (merge base-map (when name {:name name} (when address {:address address}) Or to avoid repeating field names, just form one map to be merged like {:name (function-that-may-return nil) :address (function-2)} and filter out nils from the map before merging that with-base-map. I guess with cond-> you have the advantage that you can do arbitrary map transformations, not just additions, so maybe this is just a case of nitpicking a strawman example - but doing only additions is a common case too.
- jetti 5y agoYea I used to do the merge and when as well. I'm not sure if there is an idiomatic way to do it but I personally prefer the threading style as it is "prettier".
- jetti 5y agoAnother two functions that I forgot to mention last night are filter and keep. I am actually surprise filter isn't already on the site. filter takes a predicate and a collection and will return a lazy sequence of all items that the predicate returns logically true. For example: (filter odd? [1 2 3 4]) would return a lazy sequence of (1 3) keep is a function that I was unaware of (and most of my team was too) until I was turned on to it by a former coworker. keep takes a function and a collection and returns a lazy sequence of non-nil results. For instance, if I have a collection of maps and I want to only get a value of a specific key I could do the following with map and remove (def items [{:v "hi"} {:v "there"} {:m "not v"}]) (remove nil? (map :v items)) What the map does is get the key `v` from each map in the collection and we end up with a sequence that looks like `("hi" "there" nil)` Since the last map in the collection `items` does not have a key `v` the value will return nil. We would then need to use the remove function with the nil? predicate which will remove all items in the collection that are nil. If we use keep, though, we could do this in one pass. (def items [{:v "hi"} {:v "there"} {:m "not v"}]) (keep :v items) Because keep already removes nil for us we end up with `("hi" "there")`