【问题标题】:Clojure: compose functions of arity 2 (or higher)Clojure:组合 arity 2(或更高)的函数
【发布时间】:2015-12-15 11:49:43
【问题描述】:

我正在使用可变数量的函数处理数据,具体取决于参数。每个处理函数都会从其前身接收数据,对其进行处理并将其传递给下一个函数。

(defn example [data]
  (do-things-to data))

我的申请流程是

  1. 检查参数并将所需函数存储在向量中
  2. 创建一个包含所有必要步骤的函数
  3. 调用一个包装函数来进行文件管理并最终应用该函数

样机:

(let [my-big-fun (reduce comp (filter identity) vector-of-functions)]
  (wrapper lots-a-arguments big-fun)

现在我发现我不仅需要将数据传递给函数,还需要另一个数据集。

(defn new-fun-example [root data]
  (do-things-to-both root data))

有没有办法做类似于我对 arity-1 函数所做的缩减?一个简单的juxt 是不行的,因为每个函数都会更改下一个函数需要的数据。返回'(root data) 或类似的序列将需要在许多函数中进行大量重写。

有什么想法吗?我猜答案是“宏观”,但我从来没有摆弄过这些……

编辑1:

第二个参数是对不断增长的图形数据结构的引用,因此它不需要由函数处理,只需以某种方式传递即可。 但是这些函数可能来自不同的命名空间,所以我不能简单地将根放在更高的范围内来访问它。全局def 是可能的,但极其丑陋...

在写这篇文章时,我只是想我可能会在 comping 之前以某种方式将函数映射到 partial

编辑2:

filter identity 引起了很多混乱,这不是我的问题的一部分。首先,我不应该将它包含在我的示例中。我按照快速棕色狐狸的建议解决了任务,并为有时晦涩难懂而道歉。类似解决方案的最小示例:

(defn example [root data]
  (swap! root + data))

(defn fn-chainer [vector-of-functions]
  (let [the-root (atom 0)
    ; this filter step is just required to remove erroneously apperaring nils 
    ; from the vector of functions - not part of the question
    vector-of-functions (filter identity vector-of-functions)
    ; bake the atom to the functions
    vector-of-functions (mapv #(partial % the-root) vector-of-functions)
    ; now chain each funcion's result as argument to the next one
    my-big-fun (reduce comp vector-of-functions)]
    ; let the function chain process some dataset
    (my-big-fun 5))

; test some function vectors
(fn-chainer [example])
=> 5    ; = 0 + 5
(fn-chainer [example example])
=> 10   ; = 0 + 5 +5
(fn-chainer [example nil example example nil nil])10
=> 20   ; = 0 + 5 + 5 + 5 + 5, nils removed

【问题讨论】:

  • 我不确定你如何用 arity 2 组合两个函数,因为每个函数只能产生一个输出。每个函数的输出是两个项目的序列吗?
  • 不,不是。另一方面,第二个输入(示例中为root)是常量,它是对数据结构根的引用。

标签: clojure functional-programming function-composition arity


【解决方案1】:

正如您在编辑中提到的,您确实可以将您的函数映射到包含 root 的新函数:

(mapv #(partial % root) vector-of-functions)

【讨论】:

    【解决方案2】:

    首先,我觉得这里发生了很多事情:

    1. (filter identity) 是一个转换器,但没有任何说明其他 fns 返回转换器或包装器是否需要转换器,并且鉴于其中一些将接收两个参数,我们可以安全地说它们不是转换器。你可能想要(partial filter identity)#(filter identity %)

    2. 你为什么使用(reduce comp (filter identity) vector-of-functions)而不是(apply comp (cons (partial filter identity) vector-of-functions)

    考虑到如何组合函数,因为其中一些函数接收到更多您已经拥有值的参数,您可以使用 partial:

    (let [root [1 2 3]
          other-root [4 5 6]
          vector-of-functions [(partial filter identity) example (partial my-fun-example root) (partial other-fun-example root other-root)]
          my-big-fun (apply comp vector-of-functions)]
      (wrapper lots-a-arguments big-fun))
    

    编辑:我在上面的应用组合中使用reverse 是错误的,(reduce comp [fn1 fn2 fn3]) 将在应用时返回与(apply comp [fn1 fn2 fn3]) 相同的结果

    【讨论】:

    • 好问题。 1. 我只是过滤函数的向量,所以我不会在开发阶段意外地比较 nils 2. 因为我是 clojure 新手,从来没有完全掌握 apply 的概念 :-) 会不会更适合,如果是的话 - 为什么?我刚刚在您输入答案时编辑了有关使用partial 的想法,所以我很高兴您也提出了这一建议。您的解决方案中唯一的“但是”是:向量是以编程方式填充的,所以我需要调用map?添加我不确定的partial root 部分。
    • 重新“只过滤函数向量”:所以函数向量中的元素可能是 nil,或者输入(或输出,comp 之后)数据序列中的元素?如果是前者,我会在你到达这一点之前进行过滤。函数的前提条件是 vector-of-functions 没有任何 nil。
    • 重新申请:见stackoverflow.com/questions/3153396/clojure-reduce-vs-apply。您可以使用(reduce comp ...),但它不是惯用的IMO。但你会看到有人说相反。 YMMV。无论如何,我认为首先要更清楚地了解(filter identity) 很重要。更好的是,如果我们可以将其从问题中删除:)
    • 关于“向量以编程方式填充”:它是“以编程方式”填充(通过一个接一个地连接函数,通过连接较小的函数向量等)还是“手动”填充并不重要(使用矢量文字),在这两种情况下,您都会在vector-of-fuctions 中引用矢量,因此除了(apply comp vector-of-functions)(或reduce)之外,您似乎不需要地图或其他任何东西)
    • 啊,现在我明白你的担忧了。无论如何,感谢您的解释。
    猜你喜欢
    • 2017-04-14
    • 1970-01-01
    • 1970-01-01
    • 2013-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-25
    • 1970-01-01
    相关资源
    最近更新 更多