2011-11-18 113 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)