Applying a map to a function's rest argument

只愿长相守 提交于 2019-12-19 13:40:42

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!