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

Reply via email to