On Thu, Dec 10, 2009 at 7:20 AM, Timothy Pratley
<[email protected]> 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 [email protected]
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en