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

后端 未结 8 1841
南旧
南旧 2021-01-17 12:37

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.



        
相关标签:
8条回答
  • 2021-01-17 13:23

    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)
    
    0 讨论(0)
  • 2021-01-17 13:24

    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.

    0 讨论(0)
  • 2021-01-17 13:24
    (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 ...).

    0 讨论(0)
  • 2021-01-17 13:33

    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.

    0 讨论(0)
  • 2021-01-17 13:33

    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)
    
    0 讨论(0)
  • 2021-01-17 13:37

    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 ;-).

    0 讨论(0)
提交回复
热议问题