如何在Go语言中组合函数?

3

我正在尝试弄清楚如何设置中间件,目前我的代码是这样的:

func applyMiddleware(h *Handle) *Handle {
   return a(b(c(h)))
}

有没有一种方法可以“组合”这些函数,这样我只需要传递一个Handle(s)列表,它就会返回组合的函数?

你能提供“a”,“b”或“c”的签名吗? - kingkupps
2
它们必须是 func(*Handle) *Handle,否则它不是有效的 Go 代码。 - user4466350
啊,是的,你说得对,谢谢,我没想到那个。 - kingkupps
3个回答

7

使用切片

https://play.golang.org/p/DrEnGkIEbU3

package main

import (
    "fmt"
)

func main() {
    fmt.Println(v(v(v(0))))
    fmt.Println(compose(v, v, v)(0))
}
func v(i int) int {
    return i + 1
}
func compose(manyv ...func(int) int) func(int) int {
    return func(i int) int {
        for _, v := range manyv {
            i = v(i)
        }
        return i
    }
}

我猜这实际上是管道操作而不是组合操作。代码保持不变,只是执行顺序从右到左。最内层的函数首先被执行,然后是外层函数。如果我错了,请纠正我。 - Shubhang b
1
不,这通常被视为函数组合。管道是将数据流生产者连接到数据流消费者的过程。 - user4466350

0
自从 Go 1.18 引入泛型以来,您可以定义以下通用函数来组合两个任意函数:
func compose[A any, B any, C any](f func(A) B, g func(B) C) func(A) C {
    return func(a A) C {
        return g(f(a))
    }
}

compose 的组合顺序是从左到右,即 compose(f, g) 返回函数 "f 后的 g" 或 gf,在 Haskell 中等同于 g . f,在 F# 或 Elm 中等同于 f >> g

换句话说,结果函数 compose(f, g) 的输入被馈送到 f,然后将 f 的输出馈送到 g,其输出是最终结果:

order of composition of compose(f, g)


首先,让我们定义类型Adapter来表示您想要组合的函数类型:

type Adapter = func(*Handle) *Handle

使用 composeAdapter,您现在可以定义 composeAdapters 来组合任意数量的这些 Adapter 函数:

func composeAdapters(adapters ...Adapter) Adapter {
    composition := func(h *Handle) *Handle {
        return h
    }
    for _, adapter := range adapters {
        composition = compose(composition, adapter)
    }
    return composition
}

请注意,对于*Handlecomposition被初始化为identity function。您可以将其视为no-op adapter:如果有任何要组合的第一个函数,则它只会将输入转发到结果组合适配器中。这也意味着调用没有任何参数的composeAdapters - 例如,composeAdapters() - 将导致无操作适配器:它不对输入*Handle执行任何操作;它只是将其返回。
给定类型为Adapter(即func(*Handle) *Handle)的函数fghapplyMiddleware可以实现为:
var applyMiddleware = composeAdapters(f, g, h)

请注意组合的顺序:

order of composition of applyMiddleware


0
JFMR的回答是最直接的。然而,对于喜欢函数式编程倾向的人来说...你可以通过创建一个Functor接口来组合函数。
// functor via embedding
type Functor[A any, B any, C any] interface {
    fmap(func(A) B) C
}

// holding the "function" type in a way that is compatible with Functor
type Wtf[R any, A any, B any] func(R) A

// implementation/instance of Functor for anonymous functions!
func (g Wtf[R, A, B]) fmap(f func(A) B) Wtf[R, B, A] {
    h := func(t R) B { return f(g(t)) }
    var funout Wtf[R, B, A] = h
    return funout
}


在实现中,返回类型故意选择为嵌入类型Wtf[R,B,A],而不是fn(R)B,以允许组合操作的链接。
然后展示它是如何工作的...
func main(){
    var f1 Wtf[int, int, int] = func(t int) int { return 1 * t }
    f2 := func(t int) int { return 2 * t }
    f3 := func(t int) int { return 3 * t }
    fmt.Println((f1.fmap(f2).fmap(f3))(7)) // 42 
}

请注意,函数的顺序与在其他语言中使用管道操作时所见到的顺序相同(例如 f1 |> f2 |> f3)。
这种构造的好处不仅在于它允许您将操作链接在一起,还可以为其他数据类型创建接口的其他实现。例如,当应用于集合时,它会给您带来您期望从函数式编程的map函数中看到的行为(不要与Go语言中同名的函数混淆)。
这种构造的一个好处是,类型的唯一约束是它们与组合兼容。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接