Reputation: 8841
I'm trying to figure out how to set up middlewares, and right now I've got something like:
func applyMiddleware(h *Handle) *Handle {
return a(b(c(h)))
}
Is there a way to "compose" these functions so I can just pass a list of Handle(s) and it will return the composed function?
Upvotes: 3
Views: 3094
Reputation: 61
JFMR's response is the most direct. However, for folks who like a functional-programming slant ... you could alternatively compose functions by creating a Functor
interface.
// 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
}
In the implementation, the return types was purposely chosen to be the embedded type, Wtf[R,B,A]
, rather than fn(R)B
in order to permit the chaining of composition operations.
Then to show how it works ...
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
}
Please notice that ordering of the functions is the same that you'd see when using piping operations in other languages (like f1 |> f2 |> f3
).
The nice thing about this construction is that not only does it allow you to chain operations together, but you can create other implementations of the interface for other data-types. For example, when applied to collections, it gives you the behavior that you'd expect to see from the functional-programming map
-function (not to be confused with Go's function of the same name).
A nice thing about this construction is that the only constraint on types is that they're consistent with permitting composition.
Upvotes: 0
Reputation: 24738
Since the introduction of generics in Go 1.18, you can define the following generic function for composing two arbitrary functions:
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))
}
}
The order of composition of compose
above is left to right, i.e., compose(f, g)
returns the function "g after f" or g ∘ f, equivalent to g . f
in Haskell and to f >> g
in F# or Elm.
In other words, the input to the resulting function – compose(f, g)
– is fed into f
, then f
's output is fed into g
, whose output is the final result:
Let's first define the type Adapter
for representing the type of functions you want to compose:
type Adapter = func(*Handle) *Handle
With compose
and Adapter
, you can now define composeAdapters
for composing an arbitrary number of these Adapter
s functions:
func composeAdapters(adapters ...Adapter) Adapter {
composition := func(h *Handle) *Handle {
return h
}
for _, adapter := range adapters {
composition = compose(composition, adapter)
}
return composition
}
Note that composition
is initialized to the identity function for *Handle
. You can think of it as a no-op adapter: it just forwards the input to the resulting composed adapters to the first function to compose in the chain, if any. This also implies that calling composeAdapters
without any arguments – e.g., composeAdapters()
– results in the no-op adapter: it performs no action on the input *Handle
; it just gives it back.
Given the functions f
, g
, and h
of type Adapter
– i.e., func(*Handle) *Handle
– applyMiddleware
can be implemented as:
var applyMiddleware = composeAdapters(f, g, h)
Note again the order of composition:
Upvotes: 1
Reputation:
use a slice
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
}
}
Upvotes: 7