4Clojure 问题58表述为:
编写一个允许您创建函数组合的函数.参数列表应该采用可变数量的函数,并创建一个从右到左应用它们的函数.
(= [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"))
Run Code Online (Sandbox Code Playgroud)
这__应该由解决方案取代.
在这个问题中,comp不应该使用该功能.
我找到的解决方案是:
(fn [& xs]
(fn [& ys]
(reduce #(%2 %1)
(apply (last xs) ys) (rest (reverse xs)))))
Run Code Online (Sandbox Code Playgroud)
有用.但我真的不明白reduce这里是如何运作的.它是如何代表的(apply f_1 (apply f_2 ...(apply f_n-1 (apply f_n args))...)?
让我们尝试分三个阶段修改该解决方案.保持每个人一段时间,看看你是否得到它.如果你做的话,请停下来,以免让你更加困惑.
首先,让我们有更多的描述性名称
(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)))))
Run Code Online (Sandbox Code Playgroud)
然后考虑一些
(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))))
Run Code Online (Sandbox Code Playgroud)
接下来用一个相同的循环替换reduce
(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))))))))
Run Code Online (Sandbox Code Playgroud)
我的解决方案是:
(fn [& fs]
(reduce (fn [f g]
#(f (apply g %&))) fs))
Run Code Online (Sandbox Code Playgroud)
让我们尝试:
((
(fn [& fs]
(reduce (fn [f g]
#(f (apply g %&))) fs))
#(.toUpperCase %)
#(apply str %)
take)
5 "hello world"))
Run Code Online (Sandbox Code Playgroud)
fs是功能列表:
#(.toUpperCase %)
#(apply str %)
take
Run Code Online (Sandbox Code Playgroud)
第一次通过减少,我们设置
f <--- #(.toUpperCase %)
g <--- #(apply str %)
Run Code Online (Sandbox Code Playgroud)
我们创建一个匿名函数,并将其分配给reduce函数的累加器.
#(f (apply g %&)) <---- uppercase the result of apply str
Run Code Online (Sandbox Code Playgroud)
下次通过reduce,我们设置
f <--- uppercase the result of apply str
g <--- take
Run Code Online (Sandbox Code Playgroud)
我们再次创建一个新的匿名函数,并将其分配给reduce函数的累加器.
#(f (apply g %&)) <---- uppercase composed with apply str composed with take
Run Code Online (Sandbox Code Playgroud)
fs现在为空,所以这个匿名函数从reduce返回.
这个功能通过5和"你好世界"
然后是匿名函数:
这是一个elegent(在我看来)定义comp:
(defn comp [& fs]
(reduce (fn [result f]
(fn [& args]
(result (apply f args))))
identity
fs))
Run Code Online (Sandbox Code Playgroud)
嵌套的匿名函数可能会让它一开始难以阅读,所以让我们尝试通过拉出它们并给它们命名来解决这个问题.
(defn chain [f g]
(fn [& args]
(f (apply g args))))
Run Code Online (Sandbox Code Playgroud)
这个函数chain就像comp它只接受两个参数一样.
((chain inc inc) 1) ;=> 3
((chain rest reverse) [1 2 3 4]) ;=> (3 2 1)
((chain inc inc inc) 1) ;=> ArityException
Run Code Online (Sandbox Code Playgroud)
compatop 的定义chain非常简单,有助于隔离reduce为节目带来的东西.
(defn comp [& fs]
(reduce chain identity fs))
Run Code Online (Sandbox Code Playgroud)
它将前两个函数链接在一起,其结果是一个函数.然后它将该功能链接到下一个,依此类推.
所以使用你的上一个例子:
((comp #(.toUpperCase %) #(apply str %) take) 5 "hello world") ;=> "HELLO"
Run Code Online (Sandbox Code Playgroud)
仅使用chain(否reduce)的等价物是:
((chain identity
(chain (chain #(.toUpperCase %)
#(apply str %))
take))
5 "hello world")
;=> "HELLO"
Run Code Online (Sandbox Code Playgroud)
从根本上讲,reduce是关于迭代.这是命令式样式的实现可能是什么样的(忽略了多个arities的可能性,正如Clojure的版本支持):
def reduce(f, init, seq):
result = init
for item in seq:
result = f(result, item)
return result
Run Code Online (Sandbox Code Playgroud)
它只是捕获迭代序列并累积结果的模式.我认为reduce它有一种神秘感,它实际上可以让它比它需要的更难理解,但如果你只是分解它你肯定会得到它(并且可能会惊讶你经常发现它有用).