Account Options

  1. Sign in
The old Google Groups will be going away soon, but your browser is incompatible with the new version.
Google Groups Home
« Groups Home
Message from discussion FOLLOW UP:map-utils & table-utils
The group you are posting to is a Usenet group. Messages posted to this group will make your email address visible to anyone on the Internet.
Your reply message has not been sent.
Your post was successful
 
From:
To:
Cc:
Followup To:
Add Cc | Add Followup-to | Edit Subject
Subject:
Validation:
For verification purposes please type the characters you see in the picture below or the numbers you hear by clicking the accessibility icon. Listen and type the numbers you hear
 
Sean Devlin  
View profile  
 More options Nov 24 2009, 10:04 am
From: Sean Devlin <francoisdev...@gmail.com>
Date: Tue, 24 Nov 2009 07:04:09 -0800 (PST)
Local: Tues, Nov 24 2009 10:04 am
Subject: Re: FOLLOW UP:map-utils & table-utils
Why have an idiom when you can have a function instead?  Isn't that
the whole point of Lisp?

I've been thinking for a week.  I think my previous map-utils proposal
is exactly the WRONG way to go.  I'd like to propose an entirely new
technique, based on some of the work I've done recently.

I posted this in the main thread yesterday:

http://groups.google.com/group/clojure/browse_thread/thread/7bb01c235...

Let's use a visitor pattern instead.  Way less code, way more
flexible.  Way more future-proof.

I posted an example of predicate visitors yesterday.  Here they are
again in terms of my visitor function:

;Works w/ predicate functions
(def keys-pred (visitor #(comp % key) (partial into {}))
(def vals-pred (visitor #(comp % val) (partial into {}))

Here's how the set of map functions would look.

(def keys-entry
  (visitor
    #(juxt (comp % key) val)
    (partial into {})))

(def vals-entry
  (visitor
    #(juxt key (comp % val))
    (partial into {})))

The merge case is a little more complicated:

(defn keys-entry-merge
  "Like visit keys, but takes a merge function to resolve keys
collisions."
  [merge-fn & args]
  (apply (visitor
          #(juxt (comp % key) val)
          (comp
            (partial apply merge-with merge-fn)
            (partial map (p apply hash-map))))
         args))

So, what's this look like in use?  Let's start w/ predicate fns.

user=>(def test-map {"a" 1 "b" 2 "c" 3})

user=>(vals-pred filter odd? test-map)
{"a" 1 "c" 3}

user=>(vals-pred remove odd? test-map)
{"b" 2}

user=>(keys-pred filter #{"a"} test-map)
{"a" 1}

user=>(keys-pred remove #{"a"} test-map)
{"b" 2 "c" 3}

This also works with take-while/drop-while, the visitor pattern lets
you do that.  I don't know WHY one would use them, but you could.

Here's how the entry visitors look

user=>(vals-entry map inc test-map)
{"a" 2 "b" 3 "c" 4}

user=>(keys-entry map #(.toUpperCase %) test-map)
{"A" 1 "B" 2 "C" 3}

And here's the merge case

user=>(keys-entry-merge + map (constantly "Example") test-map)
{"Example" 6}

I like that this doesn't actually produce any new sequence functions,
rather it decorates ones that already exist.  I think it's a clear win
in the predicate versions.  However, I'm not 100% sure about the entry
versions.

Can anyone think of a use case besides mapping operations?

Thanks,
Sean

On Nov 18, 10:36 am, Chouser <chou...@gmail.com> wrote:

> On Wed, Nov 18, 2009 at 12:55 AM, Sean Devlin <francoisdev...@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


 
You must Sign in before you can post messages.
To post a message you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.