2014-01-27 80 views
4

4Clojure Problem 58表述爲:Clojure的:實現補償功能


寫一個函數,它允許你創建的功能成分。參數列表應該包含可變數量的函數,並且創建一個函數從右到左應用它們。

(= [3 2 1] ((__ rest reverse) [1 2 3 4])) 

(= 5 ((__ (partial + 3) second) [1 2 3 4])) 

(= true ((__ zero? #(mod % 8) +) 3 5 7 9)) 

(= "HELLO" ((__ #(.toUpperCase %) #(apply str %) take) 5 "hello world")) 

這裏__應該被解決方案替換。

在這個問題中,不應該使用函數comp


我找到了一個解決辦法是:

(fn [& xs] 
    (fn [& ys] 
    (reduce #(%2 %1) 
      (apply (last xs) ys) (rest (reverse xs))))) 

它的工作原理。但我並不十分了解reduce在這裏的工作原理。它代表(apply f_1 (apply f_2 ...(apply f_n-1 (apply f_n args))...)

回答

8

讓我們嘗試修改3個階段的解決方案。保持一段時間,看看你是否得到它。如果當你害怕,我會更加迷惑你。

首先,讓我們有更多的描述性的名稱

(defn my-comp [& fns] 
    (fn [& args] 
    (reduce (fn [result-so-far next-fn] (next-fn result-so-far)) 
     (apply (last fns) args) (rest (reverse fns))))) 

然後因數高達一些

(defn my-comp [& fns] 
    (fn [& args] 
    (let [ordered-fns (reverse fns) 
      first-result (apply (first ordered-fns) args) 
      remaining-fns (rest ordered-fns)] 
    (reduce 
     (fn [result-so-far next-fn] (next-fn result-so-far)) 
     first-result 
     remaining-fns)))) 

下一個更換一個循環做同樣的

(defn my-comp [& fns] 
    (fn [& args] 
    (let [ordered-fns (reverse fns) 
      first-result (apply (first ordered-fns) args)] 
     (loop [result-so-far first-result, remaining-fns (rest ordered-fns)] 
     (if (empty? remaining-fns) 
      result-so-far 
      (let [next-fn (first remaining-fns)] 
       (recur (next-fn result-so-far), (rest remaining-fns)))))))) 
0

考慮這個例子降低:

(def c (comp f1 ... fn-1 fn)) 

(c p1 p2 ... pm) 

c時被調用:

  • 第一comp的最右邊的參數fn被施加到p*參數;

  • 然後fn-1適用於上一步的結果;

    (...)

  • 然後f1被應用到上一步的結果,並返回其結果

你的樣品溶液不完全一樣的。

  • 第一最右邊的參數(last xs)被施加到ys參數:

    (apply (last xs) ys) 
    
  • 其餘參數被反轉以被饋送到reduce

    (rest (reverse xs)) 
    
  • reduce需要所提供的初始結果和函數列表,並迭代應用該樂趣ctions的結果:

    (reduce #(%2 %1) ..init.. ..functions..) 
    
5

下面是一個高貴(在我看來)的comp定義:

(defn comp [& fs] 
    (reduce (fn [result f] 
      (fn [& args] 
       (result (apply f args)))) 
      identity 
      fs)) 

嵌套的匿名功能,可能使其難以在第一閱讀,所以讓我們嘗試通過拉出來並給他們一個名字來解決這個問題。

(defn chain [f g] 
    (fn [& args] 
    (f (apply g args)))) 

此功能chain類似,只是它只接受兩個參數comp

((chain inc inc) 1)    ;=> 3 
((chain rest reverse) [1 2 3 4]) ;=> (3 2 1) 
((chain inc inc inc) 1)   ;=> ArityException 

comp頂上chain的定義很簡單,有利於隔離什麼reduce帶來的演出。

(defn comp [& fs] 
    (reduce chain identity fs)) 

它將前兩個函數鏈接在一起,其結果是一個函數。然後鏈接功能與下一個,依此類推。

因此,使用您的最後一個例子:

((comp #(.toUpperCase %) #(apply str %) take) 5 "hello world") ;=> "HELLO" 

只使用chain(無reduce)相對應的是:

((chain identity 
     (chain (chain #(.toUpperCase %) 
         #(apply str %)) 
       take)) 
5 "hello world") 
;=> "HELLO" 

在最基本的層面,reduce約爲迭代。下面是一個命令行式風格的實現可能是什麼樣子(忽略多個arities的可能性,因爲Clojure的版本支持):

def reduce(f, init, seq): 
    result = init 
    for item in seq: 
     result = f(result, item) 
    return result 

這只是捕捉遍歷序列和積累的結果的模式。我認爲reduce在它周圍有一種神祕感,它實際上可能使它更難以理解,但如果你把它分解,你肯定會得到它(並且可能會驚訝你經常發現它有用) 。

+0

我真的很喜歡這個分解樣稿成鏈,降低 - 這是很容易理解。 – Mala

5

我的解決辦法是:

(fn [& fs] 
    (reduce (fn [f g] 
      #(f (apply g %&))) fs)) 

讓我們嘗試爲:

((
    (fn [& fs] 
    (reduce (fn [f g] 
       #(f (apply g %&))) fs)) 

    #(.toUpperCase %) 
    #(apply str %) 
    take) 

    5 "hello world")) 

fs是功能列表:

#(.toUpperCase %) 
#(apply str %) 
take 

第一次通過減少,我們套裝

f <--- #(.toUpperCase %) 
g <--- #(apply str %) 

我們創建一個匿名函數,並將其分配給reduce函數的累加器。通過減少

#(f (apply g %&)) <---- uppercase the result of apply str 

下一次,我們設置

f <--- uppercase the result of apply str 
g <--- take 

我們再創建一個新的匿名函數,並指定該到​​降低功能的蓄電池。

#(f (apply g %&)) <---- uppercase composed with apply str composed with take 

FS現在是空的,所以這個匿名函數是從減少返回。

這個功能是通過5和 「Hello World」 的

匿名函數,那麼:

  • 確實需要5 「你好」 變成(\^h。\ E \升\升\ O)
  • 是否適用STR成爲「你好」
  • 不toUppercase成爲「HELLO」