On Wed, Nov 18, 2009 at 12:55 AM, Sean Devlin <
francoi...@gmail.com> wrote:
>
> map-vals
> map-vals does come up on the list fairly frequently. I think this is
> worth standardizing because it's a wheel people constantly re-invent,
> and as such prone to error. To pick on your implementation in
> particular (sorry)
Heh, no need to apologize. Pick away.
> (let [f identity]
> (zipmap (keys coll) (map f (vals coll))))
>
> I suspect this is a bug waiting to happen, because I don't think that
> (keys coll) is guaranteed to return the arguments in the same order as
> (vals coll).
They will both return items in the same order as 'seq' would,
which is all the same order. Hash maps make no guarantee about
what that order *is* but it will be the same for the same
immutable hash-map object. So that particular potential bug
isn't there.
The benefits of factoring out common code are well known and
frequently discussed.
But there are a couple benefits of *not* having fns to wrap small
combinations of builtins like this:
1. People reading your code can need to be familiar with fewer
fns in order to make sense of what you're doing. zipmap,
key, map, and vals are all more commonly used and therefore
more likely known to the reader than map-vals is likely to
be.
2. If any of those builtins are not known to the reader, when
they learn about them they will be learning more broadly
useful functions. zipmap is useful in more different
scenarios than map-vals, so taking the time to learn what it
does will provide bigger bang for the buck.
3. When you're familiar with the builtins, the variety of ways
they can be used, and some common idioms for using them, you
have power to solve problems that are similar but slightly
different. For example, say you want to map on both keys
and values -- if you know the zipmap idiom above, the
solution is obvious. If you only know about map-keys and
map-vals you're likely to be driven to clumsier and less
efficient combination of the two.
So when writing my own helper functions, I try to weigh the
benefits of each. In general the larger and more complex the
repeated code, the more likely I am to favor factoring it out.
But honestly I'm frequently pretty ambivalent about my
conclusions, especially on these smallish functions.
> trans/trans*
> Before I get to involved, the name of these closures is negotiable.
> That being said, let's go...
>
> trans is designed to be used for adapting maps. The idea was to
> combine it with destructuring to increase re-use, amongst other
> things. It's common to have some type of fn like this:
>
> (defn my-common-fn [{a :a b :b}] (* a b))
>
> However, NONE of the half dozen data sources you query mention :a
> or :b. They call it :pizza :barbeque or :dog :cat. Write enough
> business software and you actually see this.
Ok, now we're talking. I was writing code like this just
a couple days ago. For business no less.
> So, why does tran return a closure, instead of doing the work
> directly? This has to do with how I use trans. It's usually in a
> situation like:
>
> * function composition, e.g. (comp my-common-fn
> (trans :a :pizza :b :barbeque))
> * an argument in map (map (trans :a :pizza ...) ...)
> * an argument in my join library
>
> Also, you may have noticed trans is variadic. Since it returns a
> closure, I don't have to worry about where to place arguments in
> partial.
Hmmm... In my case I just wrote stuff like:
(use '[clojure.set :only (rename-keys)])
(-> input-map
(rename-keys {:id :new-id, :person-name :name})
(select-keys [:new-id :name :foo :bar]))
It seems to me the 'count' and 'inc' of key "a" from the examples
in your docs would fit into a -> form like this pretty nicely,
without generating extra closures.
On the other hand it would be wordier than your trans examples.
I guess if you find 'trans' compelling I'll quit whining about
it.
> map-keys
[snip]
> It should be easy enough to reuse everything, but
> you can't, because some XML library accidently capitalized
> everything. Stupid XML.
ok ok, you've convinced me. stupid xml.
> map-keys to the rescue. For each hashmap you simply do the following
>
> (map-keys (comp keyword #(.toLowerCase %) name) an-entry)
To me, the merge-with case is the most compelling. If you don't
need merge-with, zipmap will again work nicely and pretty
succinctly:
(zipmap (map (comp keyword #(.toLowerCase %) name) (keys x)) (vals x))
But if you're going to have key collisions, zipmap's not going to
cut it and you need to rework your whole expression to use
merge-with instead. ...which is also a good reason to have one
fn (map-keys) that can handle both cases without disruption.
It might be worth calling out explicitly in the docstring that
f applies to keys but merge-fn applies to values.
--Chouser