我目前正在努力完成创建匿名函数的任务,以完成以下测试用例:
测试用例 1:
(= [3 2 1] ((__ rest reverse) [1 2 3 4]))
测试用例 2:
(= 5 ((__ (partial + 3) second) [1 2 3 4]))
测试用例 3:
(= true ((__ zero? #(mod % 8) +) 3 5 7 9))
测试用例 4:
(= "HELLO" ((__ #(.toUpperCase %) #(apply str %) take) 5 "hello world"))
我想出了解决方案:
(fn [& fs]
(fn [& items] (reduce #(%2 %1)
(flatten items)
(reverse fs))))
我的想法是创建一个绑定到外部函数的函数列表,然后在这个函数列表上应用一个reducer,从数组“items”开始。 由于这适用于在测试用例 1 和 2 中链接单个元函数,我不知道如何修改内部 Lambda 函数以处理多元函数:
(apply + ___ ) ;; first function argument of test case 3
(take 5 ___ ) ;; first function argument of test case 4
还有办法解决这个问题吗? 非常感谢!
附录:我遇到了一个“时髦”的解决方案:
(fn [& fs] (reduce (fn [f g] #(f (apply g %&))) fs))
老实说,我不完全理解这种方法...
附录 2: 7 年前就有关于这个话题的类似讨论: Clojure: Implementing the comp function
在那里我找到了以下解决方案:
(fn [& xs]
(fn [& ys]
(reduce #(%2 %1)
(apply (last xs) ys) (rest (reverse xs)))))
然而,我仍然不明白我们如何能够在表达式 (apply (last xs) ys)
上启动 reducer,它代表函数链中最左边的函数。
在测试用例 1 中,这将转换为 (apply rest [1 2 3 4])
,这是错误的。
答案 0 :(得分:2)
前两个测试用例具有其余参数:git checkout -b feature2
,而不是 [[1 2 3 4]]
。
所以不是 [1 2 3 4]
而是 (apply rest [1 2 3 4])
或 (apply rest [[1 2 3 4]])
。
把它钻回家:
(rest [1 2 3 4])
使用 (rest-ex [& rst]
rst
)
(rst 1 2 3) ;;=> [1 2 3]
(rst [1 2] 3) ;;=> [[1 2] 3]
(rst [1 2 3]) ;;=> [[1 2 3]]
:
apply
对于您的时髦解决方案和 ; rest example one
(apply + [1 2 3]) ;;=> 6
; rest example two
(apply conj [[1 2] 3]) ;;=> [1 2 3]
; rest example three
(apply reverse [[1 2 3]]) ;;=> (3 2 1)
本身,这就像开车(第一个功能),用涡轮增压,安装扬声器(以下功能)。这辆车带有涡轮增压和惊人的音响系统,可供下一组朋友使用(comp
将它从一个单座的库存车变成拥有任意数量的“座位”)。在您的情况下,reducer 函数使用带有 rest 参数的 apply
,因此这就像为添加了每个函数的更多门提供选项(但它无论如何都会选择一扇门)。
前两个测试用例很简单,reduce 不需要但可以使用。
apply
第三个测试用例,在第二轮 ;; [[1 2 3 4]]
;; [rest reverse]
((fn [& fs] (reduce (fn [f g] #(f (apply g %&))) fs)) rest reverse) ;; is functionally equivalent to
((fn [& fs] #((first fs) (apply (second fs) %&))) rest reverse)
#(rest (apply reverse %&))
;; So
(((fn [& fs] (reduce (fn [f g] #(f (apply g %&))) fs)) rest reverse) [1 2 3 4]) ;; (3 2 1)
(((fn [& fs] #((first fs) (apply (second fs) %&))) rest reverse) [1 2 3 4]) ;; (3 2)
(#(rest (apply reverse %&)) [1 2 3 4]) ;;=> (3 2 1)
开始后,看起来像:
reduce
请注意 reducer 函数中的 ;; [3 5 7 9]
;; [zero? #(mod % 8) +]
;; ^ ^ The reducer function runs against these two f's
;; Which turns the original:
(fn [& fs] (reduce (fn [f g] #(f (apply g %&))) fs))
;; into an equivalent:
(reduce #(zero? (apply (fn [v] (mod v 8)) [g])) [+])
;; which ultimately results in (wow!):
((fn [& args] (zero? (apply (fn [v] (mod v 8)) [(apply + args)]))) 3 5 7 9)
。这就是我将 %&
包裹在向量中的原因。
在经历这个过程时,我意识到我从使用 (apply + args)
中得到的直觉比我意识到的要复杂一些——尤其是。带有函数组合、rest 参数和 reduce
。
事情没那么简单,但可以理解。
答案 1 :(得分:2)
这与在 clojure.core 中实现 comp 的方式非常相似。
(defn my-comp
([f] f)
([f g]
(fn
([] (f (g)))
([x] (f (g x)))
([x y] (f (g x y)))
([x y & args] (f (apply g x y args)))))
([f g & fs]
(reduce my-comp (list* f g fs))))
理解像comp这样的高阶函数的关键是考虑我们组合函数时需要发生什么。 最简单的情况是什么? (comp f) Comp 只接收一个函数,所以我们只返回那个函数,还没有组合。第二种最简单的情况如何:Comp 接收两个函数,例如 (comp f g),现在我们需要返回另一个函数,该函数在调用时进行组合,例如 (f (g))。但是这个返回的函数需要支持零个或多个参数,所以我们使它成为可变参数。为什么它需要支持零个或多个参数?由于函数 g,最里面的函数可以有零个或多个参数。
例如: (comp dec inc) 返回什么? 它返回这个 fn:
(fn
([] (dec (inc)))
([x] (dec (inc x)))
([x y] (dec (inc x y)))
([x y & args] (dec (apply inc x y args)))))
它假设 inc(最先执行的最里面的函数)可以接收零个或多个参数。但实际上 inc 只支持一个参数,所以如果你用多个这样的参数调用这个函数,你会得到 arity 异常 ((comp dec inc) 1 2),但用单个调用它参数会起作用,因为最里面的函数 inc 有一个单数,((comp dec inc) 10)。我希望我在这里清楚,为什么这个返回的函数需要是可变参数。
现在进行下一步,如果我们组合三个或更多函数怎么办?现在这很简单,因为面包和黄油已经用 my-comp 支持的两个参数函数实现了。所以我们只调用这个 2 参数函数,同时我们减少提供的函数列表。每一步都返回一个新函数,该函数包装了输入函数。