2011-11-18 110 views
12

每隔一段时间我都会发现自己想要在多个参数集合上应用一组函数。使用map和一个非常简单的功能很容易。(fn [f&args](apply f args))的标准版本或习惯用法

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

(-1 6 27) 

搜索网络时,出现了很多定义类似函数的库,通常称为funcall或invoke。由于Clojure对可变参数函数的偏爱,我不禁想到应该已经有了这个函数的默认版本。

有没有,还是有另一种惯用的方式来解决这样的情况?

编辑:

另一种形式可能是

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

(-1 6 27) 

这让我害怕,因为的eval。

回答

6

在标准Clojure库中没有funcall或等效函数,这种工作方式正是如此。 “应用”非常接近,但最终需要一系列参数,而不是纯粹的可变参数。

考虑到这一点,你可以“骗”与应用,使其工作,加入尼尔斯的无限列表的末尾,如下所示(其中获得视为额外的参数空序列):

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

总体来说,我认为明智的做法,如果你真的想经常使用这个功能只是定义它:

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

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

所以你说的是如果最后一个应用参数是零,它不知何故被解释为一个序列? (申请+ 1 2 3无)的工作。相当有趣,我希望这是故意的,因为它可能会非常方便。 – NielsK

+1

是的 - 在Clojure中,nil被认为是一个几乎一致的空序列。例如(vec nil)=> []或(seq [])=> nil。你可以依靠这种行为。 – mikera

7

编辑:这会做你想要的(如@BrandonH提到):

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

但是,这是很难在你的版本有所改进 - 它只是使用了匿名函数的简写。


我的理解是,FUNCALL是Common Lisp的必要,因为它是一个Lisp-2,而Clojure是一个Lisp-1。

+1

我的功能的结果是(-1 6 27),你的([-1 3 1] [-2 6 8] [-3 9 27])。编辑我的问题,这样的意思应该更清楚。 – NielsK

+0

@NielsK - 废话,我目前无法访问Clojure。我必须等待弄清楚我搞砸了什么。 –

+0

没问题,一直在考虑这些行(juxt),但不知怎的,最后调用解决方案是最简单的。 – NielsK

0

我现在不能对clojure.core函数的事情,你可以插入到你的地图,并让它做你想做的。所以,我会说,只使用你自己的版本。

马特可能是正确的,没有一个funcall,原因是你几乎不需要它在一个Lisp-1(含义,功能和其他绑定共享Clojure中的同一个名称空间)

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

(-1 6 27) 

问题是,如果要允许可变数量的参数,则&语法会将值放入向量中,因此必须使用apply。您的解决方案对我来说看起来很不错,但正如Brandon H指出的那样,您可以将其缩短至#(apply %1 %&)

至于其他的应答者所指出的,它没有任何关系funcall,我认为在其他的Lisp是为了避免符号和功能(注意,我调用的函数为(%1 ...)这里,不(funcall %1 ...)

+1

(地图#(申请%1%&)[ - + *] [1 2 3] [1 2 3] [1 2 3])在我看来是完全可以接受的。 –

+1

我同意,但它与提交者版本基本相同。 –

10
之间的模糊

如果你真的没有一个关于函数名的线索,但你知道的输入和输出必须是,你可以尝试https://github.com/Raynes/findfn

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

这就告诉我们,

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

其实,你可以滥用蹦床作为clojure的funcall。但它不是惯用的,因为它是一个Lisp-1。上面的代码评估为:

[(trampoline - 1 1 1), (trampoline + 2 2 2), (trampoline * 3 3 3)]然后变成 [-1 6 27](以lazyseq的形式为准确)。

正如Adrian Mouat在下面的评论中指出的,这可能不是解决问题的首选方法。使用像funcall一样的构造闻起来有点有趣。必须有一个更清洁的解决方案。直到你找到一个,findfn可以是有用的;-)。

+2

我投票赞成一个非常有趣的答案,但我不会建议这样解决它! –

+0

我同意,这就是为什么答案的第一部分是:“这是什么juxt是”... –

+1

仍然不相信有关juxt部分,马特的(删除编辑)答案给出了错误的结果。 (map(apply juxt [ - + *])[1 2 3] [1 2 3] [1 2 3])给出结果([-1 3 1] [-2 6 8] [-3 9 27]) (-1.627)。看起来像findfn可能会非常有用! – NielsK

2

我个人认为你的第一个版本非常清晰和习惯。

这里是你可能会觉得有趣但考虑一种替代方案:

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

=> (-1 6 27) 

注意事项使用的伎俩(图矢量的....),以参数的顺序调换成([1 1 1] [2 2 2] [3 3 3]),以便它们可以在应用函数中使用。

0

这件怎么样?它从juxt中选择相关的返回值。由于这是懒惰的,它应该只计算所需的元素。

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

那么,它使用juxt,它的工作原理,但我怀疑它更简洁或习惯:) – NielsK

1

另一种方法是相当自我解释:“对于每一个第n功能,其应用到向量的所有第n个元素”:

(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)