问题
In Clojure, if I have a function f,
(defn f [& r] ... )
and I have a seq args with the arguments I want to call f with, I can easily use apply:
(apply f args)
Now, say I have another function g, which is designed to take any of a number of optional, named arguments - that is, where the rest argument is destructured as a map:
(defn g [& {:keys [a b] :as m}] ... )
I'd normally call g by doing something like
(g :a 1 :b 2)
but if I happen to have a map my-map with the value {:a 1 :b 2}, and I want to "apply" g to my-map - in other words, get something that would end up as the above call, then I naturally couldn't use apply, since it would be equivalent to
(g [:a 1] [:b 2])
Is there a nice way to handle this? May I have gone off track in my design to end up with this? The best solution I can find would be
(apply g (flatten (seq my-map)))
but I surely don't like it. Any better solutions?
EDIT: A slight improvement to the suggested solution might be
(apply g (mapcat seq my-map))
which at least removes one function call, but it may still not be very clear what's going on.
回答1:
I have stumbled into this problem myself and ended up defining functions to expect one map. A map can have a variable amount of key/value pairs, and if flexible enough, so there is no need for & rest arguments. Also there is no pain with apply. Makes life a lot easier!
(defn g [{:keys [a b] :as m}] ... )
回答2:
There is no better direct way than converting to a seq.
You are done. You have done all you can.
It's just not really clojurish to have Common Lisp style :keyword arg functions. If you look around Clojure code you will find that almost no functions are written that way.
Even the great RMS is not a fan of them:
"One thing I don't like terribly much is keyword arguments (8). They don't seem quite Lispy to me; I'll do it sometimes but I minimize the times when I do that." (Source)
At the moment where you have to break a complete hash map into pieces just to pass all of them as keyword mapped arguments you should question your function design.
I find that in the case where you want to pass along general options like :consider-nil true you are probably never going to invoke the function with a hash-map {:consider-nil true}.
In the case where you want to do an evaluation based on some keys of a hash map you are 99% of the time having a f ([m & args]) declaration.
When I started out defining functions in Clojure I hit the same problem. However after thinking more about the problems I tried to solve I noticed myself using destructoring in function declaration almost never.
回答3:
Here is a very simplistic function which may be used exactly as apply, except that the final arg (which should be a map) will be expanded out to :key1 val1 :key2 val2 etc.
(defn mapply
[f & args]
(apply f (reduce concat (butlast args) (last args))))
I'm sure there are more efficient ways to do it, and whether or not you'd want to end up in a situation where you'd have to use such a function is up for debate, but it does answer the original question. Mostly, I'm childishly satisfied with the name...
回答4:
Nicest solution I have found:
(apply g (apply concat my-map))
来源:https://stackoverflow.com/questions/16347314/applying-a-map-to-a-functions-rest-argument