Here's the thing I can't stand about keyword args:

Let's start off with a simple function that looks for keys x and y, and if
either is missing,
replaces the value with 1 or 2 respectively.

(defn f [& {:keys [x y] :or {x 1 y 2}}]
  [x y])

=> (f :x 10)
[10 2]

So far, so good.

Now, let's do an extremely simple test of composability.  Let's define a
function g that destructures the keyword args, and if a certain keyword
:call-f is set, then we're just going to turn around and call f, passing
all the keyword args along to f.

(defn g [& {call-f :call-f :as m}]
  (when call-f
    (apply f m)))

=> (g :call-f true :x 10)
[1 2]

What?  Oh right, you can't apply the function f to the map m.  This doesn't
work.  If we want to "apply" f, we somehow need to apply it to a sequence
of alternating keys and values, not a map.

Take 2:

(defn g [& {:keys [call-f x y] :as m}]
  (when call-f
    (f :x x :y y)))

OK, so this time we try to workaround things by explicitly calling out the
names of all the keywords we want to capture and pass along.  It's ugly,
and doesn't seem to scale well to situations where you have an unknown but
at first glance, it seems to work:

=> (g :call-f true :x 80 :y 20)
[80 20]

Or does it?

=> (g :call-f true :x 10)
[10 nil]

What is going on here?  Why is the answer coming out that :y is nil, when
function f explicitly uses :or to have :y default to 2?

The answer is that :or doesn't do what you think it does.  The word "or"
implies that it substitutes the default value of :y any time the
destructured :y is nil or false.  But that's not how it really works.  It
doesn't destructure and then test against nil; instead the :or map only
kicks in when :y is actually missing as a key of the map.

This means that in g, when we actively destructured :y, it got set to a
nil, and then that got passed along to f.  f's :or map didn't kick in
because :y was set to nil, not absent.

This is awful.  You can't pass through keyword arguments to other functions
without explicitly destructuring them, and if you destructure them and pass
them along explicitly, nil values aren't picked up as absent values, so the
:or default maps don't work properly.

To put it simply, keyword args are bad news for composability.

It's a shame, and I'd love to see this improved (rather than just having
the community give up on keyword arguments).

-- 
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
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to