具有可变函数参数的函数组合

时间:2021-01-28 13:50:53

标签: clojure

我目前正在努力完成创建匿名函数的任务,以完成以下测试用例:

  • 测试用例 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

还有办法解决这个问题吗? 非常感谢!

来源: 4Clojure - Problem 58

附录:我遇到了一个“时髦”的解决方案:

(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]),这是错误的。

2 个答案:

答案 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 参数函数,同时我们减少提供的函数列表。每一步都返回一个新函数,该函数包装了输入函数。

相关问题