On Thu, Dec 10, 2009 at 7:20 AM, Timothy Pratley <timothyprat...@gmail.com> wrote: > Hi, > > update-in is an especially useful function but I find the update > function inevitably requires a check for nil. If I could supply a not- > found value then my code would get better golf scores. > > When I reach for update-in, I usually want to pass it a numerical > operator like inc or +, but these don't play nicely with nil. Another > scenario is when I want to pass conj, which is fine if I want to > create lists, except if I usually want the data structure to be > something else. I've never come across a scenario where I didn't want > to supply a not-found value, are there any common ones? > > If others have similar experience perhaps it is a candidate for > change. Ideally I'd like to see a not-found parameter added to update- > in and an extra arity overload for get-in as outlined below: > > (defn update-in2 > "'Updates' a value in a nested associative structure, where ks is a > sequence of keys and f is a function that will take the old value > and any supplied args and return the new value, and returns a new > nested structure. If any levels do not exist, hash-maps will be > created. If there is no value to update, default is supplied to f. " > ([m [k & ks] not-found f & args] > (if ks > (assoc m k (apply update-in2 (get m k) ks f args)) > (assoc m k (apply f (get m k not-found) args))))) > > user=> (reduce #(update-in2 %1 [%2] 0 inc) {} ["fun" "counting" > "words" "fun"]) > {"words" 1, "counting" 1, "fun" 2} > user=> (reduce #(update-in2 %1 [(first %2)] [] conj (second %2)) {} > [[:a 1] [:a 2] [:b 3]]) > {:b [3], :a [1 2]} > > > (defn get-in2 > "returns the value in a nested associative structure, where ks is a > sequence of keys" > ([m ks] > (reduce get m ks)) > ([m ks not-found] > (if-let [v (reduce get m ks)] > v > not-found))) > > user=> (get-in2 {:a {:b 1}} [:a :b] 0) > 1 > user=> (get-in2 {:a {:b 1}} [:a :b :c] 0) > 0 > > Changing update-in would be a breaking change unfortunately. To avoid > this you could consider checking the argument type for f to be > function or value (making an assumption here that you would rarely > want a function as the not-found value which is not 100% watertight). > Or you could have a similarly named update-in-or function (which is > less aesthetically pleasing), or maybe there is another even better > way? >
The get-in function could be enhanced, and would mirror get. As for update-in, a breaking change and type testing is out of the question. However, the general case is one of applying functions to missing/nil arguments that don't expect them, or whose behavior given nil you'd like to change. If it is just a substitution (and it often is, as you desire in update-in), something like this could cover many applying functions, without having to add extra what-to-do-if-nil arguments: (defn fnil "Takes a function f, and returns a function that calls f, replacing a nil first argument to f with the supplied value x. Higher arity versions can replace arguments in the second and third positions (y, z). Note that the function f can take any number of arguments, not just the one(s) being nil-patched." ([f x] (fn ([a] (f (if (nil? a) x a))) ([a b] (f (if (nil? a) x a) b)) ([a b c] (f (if (nil? a) x a) b c)) ([a b c & ds] (apply f (if (nil? a) x a) b c ds)))) ([f x y] (fn ([a b] (f (if (nil? a) x a) (if (nil? b) y b))) ([a b c] (f (if (nil? a) x a) (if (nil? b) y b) c)) ([a b c & ds] (apply f (if (nil? a) x a) (if (nil? b) y b) c ds)))) ([f x y z] (fn ([a b] (f (if (nil? a) x a) (if (nil? b) y b))) ([a b c] (f (if (nil? a) x a) (if (nil? b) y b) (if (nil? c) z c))) ([a b c & ds] (apply f (if (nil? a) x a) (if (nil? b) y b) (if (nil? c) z c) ds))))) usaage: ((fnil + 0) nil 42) -> 42 ((fnil conj []) nil 42) -> [42] (reduce #(update-in %1 [%2] (fnil inc 0)) {} ["fun" "counting" "words" "fun"]) ->{"words" 1, "counting" 1, "fun" 2} (reduce #(update-in %1 [(first %2)] (fnil conj []) (second %2)) {} [[:a 1] [:a 2] [:b 3]]) -> {:b [3], :a [1 2]} fnil seems to me to have greater utility than patching all functions that apply functions with default-supplying arguments. Rich -- You received this message because you are subscribed to the Google Groups "Clojure" group. To post to this group, send email to clojure@googlegroups.com Note that posts from new members are moderated - please be patient with your first post. To unsubscribe from this group, send email to clojure+unsubscr...@googlegroups.com For more options, visit this group at http://groups.google.com/group/clojure?hl=en