Standard version or idiomatic use of (fn [f & args] (apply f args))

╄→гoц情女王★ 提交于 2019-12-19 13:31:16

问题


Every so often I find myself wanting to apply a collection of functions on several collections of parameters. It's easy to do with map and a very simple function.

(map
  (fn invoke [f & args] (apply f args))
  [- + *]
  [1 2 3]
  [1 2 3]
  [1 2 3])

 (-1 6 27)

Searching the web turns up quite a few libraries that define a similar function, often called funcall or invoke. Because of Clojure's penchant for variadic functions, I cannot help but think there should already be a default version of this function.

Is there, or is there another idiomatic way to solve situations like this ?

Edit:

Another form may be

(map
  (comp eval list)
  [- + *]
  [1 2 3]
  [1 2 3]
  [1 2 3])

 (-1 6 27)

Which scares me because of the eval.


回答1:


There isn't a funcall or equivalent function in the standard Clojure library that works exactly this way. "apply" is pretty close but needs a collection of arguments at the end rather than being purely variadic.

With this in mind, you can "cheat" with apply to make it work as follows by adding an infinite list of nils to the end (which get considered as empty sequences of additional arguments):

(map apply [- + *] [1 2 3] [1 2 3] [1 2 3] (repeat nil))
=> (-1 6 27)

Overall though, I think the sensible approach if you really want to use this function frequently is just to define it:

(defn funcall [f & ps]
  (apply f ps))

(map funcall [- + *] [1 2 3] [1 2 3] [1 2 3])
=> (-1 6 27)



回答2:


If you really don't have a clue about the function name, but you know what the in- and output have to be, you can try https://github.com/Raynes/findfn.

(find-arg [-1 6 27] map '% [- + *] [1 2 3] [1 2 3] [1 2 3])
;=> (clojure.core/trampoline)

This tells us that

(map trampoline [- + *] [1 2 3] [1 2 3] [1 2 3])
;=> (-1 6 27)

Actually, you can abuse trampoline as funcall in clojure. But it is hardly idiomatic, because it is a Lisp-1. The above code evaluates to:

[(trampoline - 1 1 1), (trampoline + 2 2 2), (trampoline * 3 3 3)] which then becomes [-1 6 27] (in the form a of lazyseq to be precise).

As Adrian Mouat points out in the comment below, this probably isn't the preferred way to solve it. Using a funcall like construct smells a bit funny. There must be a cleaner solution. Until you've found that one, findfn can be helpful ;-).




回答3:


Edit: this will do what you want (as @BrandonH mentioned):

(map #(apply %1 %&) [- + *] [1 2 3] [1 2 3] [1 2 3])

But this is hardly an improvement over your version -- it just uses a shorthand for anonymous functions.


My understanding is that FUNCALL is necessary in Common Lisp, as it's a Lisp-2, whereas Clojure is a Lisp-1.




回答4:


(map #(%1 %2 %3 %4) [- + *][1 2 3][1 2 3][1 2 3])

(-1 6 27)

The problem is that if you want to allow a variable number of arguments, the & syntax puts the values in a vector, necessitating the use of apply. Your solution looks fine to me but as Brandon H points out, you can shorten it to #(apply %1 %&).

As the other answerers have noted, it has nothing to do with funcall which I think is used in other Lisps to avoid ambiguity between symbols and functions (note that I called the function as (%1 ...) here, not (funcall %1 ...).




回答5:


I personally think your first version is pretty clear and idiomatic.

Here's an alternative you might find interesting to consider however:

(map 
  apply 
  [- + *] 
  (map vector [1 2 3] [1 2 3] [1 2 3]))

=> (-1 6 27)

Note the trick of using (map vector ....) to transpose the sequence of arguments into ([1 1 1] [2 2 2] [3 3 3]) so that they can be used in the apply function.




回答6:


Another approach which is fairly self explanatory: "for each nth function, apply it to all nth elements of the vectors":

(defn my-juxt [fun-vec & val-vecs]
  (for [n (range 0 (count fun-vec))]
    (apply (fun-vec n) (map #(nth % n) val-vecs))))

user> (my-juxt [- + *] [1 2 3] [1 2 3] [1 2 3])
(-1 6 27)



回答7:


I can't right now thing of a clojure.core function that you could plug into your map and have it do what you want. So, I'd say, just use your own version.

Matt is probably right that the reason there isn't a funcall, is that you hardly ever need it in a Lisp-1 (meaning, functions and other bindings share the same name space in clojure)




回答8:


What about this one? It selects the relevant return values from juxt. Since this is all lazy, it should only calculate the elements needed.

user> (defn my-juxt [fns & colls] 
        (map-indexed (fn [i e] (e i))
          (apply map (apply juxt fns) colls)))
#'user/my-juxt
user> (my-juxt [- + *] [1 2 3] [1 2 3] [1 2 3])
(-1 6 27)


来源:https://stackoverflow.com/questions/8182956/standard-version-or-idiomatic-use-of-fn-f-args-apply-f-args

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