Writing Go in 2025: No More if err != nil

A functional style for real-world Go code

View the go-fp repository on GitHub

Index

go-fp logo

Intro

In 2025, I don’t write Go like it’s 2015. I write Go functionally. No more if err != nil scattered across my code. No more deeply nested error handling. Just monads.

I built a small functional programming package for Go: it gives me Lift, LiftM, Bind, Map, Then, Match, and many more both immutable chains and mutable wrappers. That’s all I need to write readable, composable, and safe Go code.

The Client Example

Full example on github

Here’s how a typical HTTP client looks using this approach:


func fetchUsers() immutable.Chain[[]User] {
    urlChain := immutable.Wrap("http://localhost:8080/users")
    respChain := immutable.Bind(urlChain, GetChain)
    usersChain := immutable.Bind(respChain, parseUsers)
    return usersChain
}

The functions being composed are also lifted. For example:


func GetChain(url string) immutable.Chain[*http.Response] {
    return immutable.LiftResult(func() (*http.Response, error) {
        return http.Get(url)
    })
}

Chaining IO and JSON decoding becomes a matter of composition:


func parseUsers(resp *http.Response) immutable.Chain[[]User] {
    defer resp.Body.Close()
    bodyChain := immutable.Wrap(resp.Body)
    dataChain := immutable.Bind(bodyChain, ReadAllChain)
    usersChain := immutable.Bind(dataChain, UnmarshalChain[[]User])
    return usersChain
}

In main(), I process the chain:


usersChain := fetchUsers()

usersChain.
    Then(func(users []User) ([]User, error) {
        var filtered []User
        for _, u := range users {
            if u.Age > 25 {
                filtered = append(filtered, u)
            }
        }
        return filtered, nil
    }).
    Map(func(users []User) []User {
        fmt.Println("Users older than 25:")
        for _, u := range users {
            fmt.Printf("  ID:%d Name:%s Age:%d\n", u.ID, u.Name, u.Age)
        }
        return users
    }).
    Match(nil, func(err error) {
        log.Println("Error fetching users:", err)
    })

How It Works

I started using this for everything: servers, clients, DB access, CLI tools. This is Go, but with a functional core. Just enough abstraction. Just enough control.