user1354934
user1354934

Reputation: 8841

How to compose functions in go?

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

Answers (3)

David Reynolds
David Reynolds

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

jfMR
jfMR

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 gf, 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:

order of composition of compose(f, g)


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 Adapters 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) *HandleapplyMiddleware can be implemented as:

var applyMiddleware = composeAdapters(f, g, h)

Note again the order of composition:

order of composition of applyMiddleware

Upvotes: 1

user4466350
user4466350

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

Related Questions