{"id":47929302,"url":"https://github.com/tomasbasham/gofp","last_synced_at":"2026-04-04T07:12:08.032Z","repository":{"id":340247762,"uuid":"1149243855","full_name":"tomasbasham/gofp","owner":"tomasbasham","description":"A module providing monadic structures for functional programming patterns","archived":false,"fork":false,"pushed_at":"2026-02-23T23:11:44.000Z","size":51,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-02-24T05:45:46.745Z","etag":null,"topics":["golang"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tomasbasham.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-03T22:19:53.000Z","updated_at":"2026-02-23T23:11:39.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/tomasbasham/gofp","commit_stats":null,"previous_names":["tomasbasham/gofp"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/tomasbasham/gofp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomasbasham%2Fgofp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomasbasham%2Fgofp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomasbasham%2Fgofp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomasbasham%2Fgofp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tomasbasham","download_url":"https://codeload.github.com/tomasbasham/gofp/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tomasbasham%2Fgofp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31390945,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T04:26:24.776Z","status":"ssl_error","status_checked_at":"2026-04-04T04:23:34.147Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["golang"],"created_at":"2026-04-04T07:12:07.317Z","updated_at":"2026-04-04T07:12:08.012Z","avatar_url":"https://github.com/tomasbasham.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# gofp [![test](https://github.com/tomasbasham/gofp/actions/workflows/test.yaml/badge.svg?event=push)](https://github.com/tomasbasham/gofp/actions/workflows/test.yaml)\n\nA Go module providing monadic structures for functional programming patterns. It\noffers type-safe containers for handling optionality, errors, state,\ndependencies, and computations in a composable manner.\n\nWhilst Go is primarily an imperative language, certain patterns from functional\nprogramming can reduce boilerplate, improve type safety, and make complex\ncontrol flow more explicit. This library provides battle-tested abstractions for\ndevelopers who want these benefits without sacrificing Go's simplicity.\n\n## Prerequisites\n\nYou will need the following things properly installed on your computer:\n\n- [Go](https://golang.org/): any one of the **three latest major**\n  [releases](https://golang.org/doc/devel/release.html)\n\n## Installation\n\nWith [Go module](https://go.dev/wiki/Modules) support (Go 1.11+), simply add the\nfollowing import\n\n```go\nimport \"github.com/tomasbasham/gofp\"\n```\n\nto your code, and then `go [build|run|test]` will automatically fetch the\nnecessary dependencies.\n\nOtherwise, to install the `gofp` module, run the following command:\n\n```bash\ngo get -u github.com/tomasbasham/gofp\n```\n\n## Philosophy\n\nThis library embraces functional programming patterns whilst respecting Go's\npragmatic nature. It's a tool, not a religion. Use it where it adds clarity and\nsafety, and reach for standard Go patterns where they're clearer.\n\nMonads provide:\n- **Explicit handling of edge cases** - No more forgotten nil checks\n- **Composable operations** - Chain transformations declaratively\n- **Type safety** - The compiler ensures you handle all cases\n- **Reduced boilerplate** - Especially for error handling pipelines\n\nBut remember: Go is not Haskell. This library works best when integrated\nthoughtfully into Go codebases, not when used to fight against the language's\nidioms.\n\n## Usage\n\nTo use this module, import the relevant packages into your Go code. The core\ntypes (`Option`, `Result`, `Either`) are available directly from the main\npackage, whilst more specialised monads (`Reader`, `State`, `Writer`) are in\ntheir own subpackages:\n\n```go\nimport (\n    \"github.com/tomasbasham/gofp\"\n    \"github.com/tomasbasham/gofp/reader\"\n    \"github.com/tomasbasham/gofp/state\"\n    \"github.com/tomasbasham/gofp/writer\"\n)\n```\n\nEach monad provides a consistent interface with `Map` and `FlatMap` methods for\ntransforming and composing computations. The library follows common functional\nprogramming conventions, where `Map` transforms the contained value and\n`FlatMap` (also called \"bind\" in other languages) chains operations that\nthemselves return monadic values.\n\nBegin by identifying which monad fits your problem domain, then compose\noperations using the provided combinators. The examples below demonstrate\ntypical usage patterns for each monad.\n\n## Available Monads\n\n### Option[T]\n\nRepresents an optional value that may or may not exist. Eliminates nil pointer\nerrors and makes optionality explicit in your type signatures.\n\n**Mathematical form:**\n\n```text\nOption T = Some T | None\n```\n\n**When to use:**\n- Database queries that may return no results\n- Configuration values that might be missing\n- Function parameters that are genuinely optional\n- Parsing operations that might fail\n\n**Example:**\n\n```go\nimport \"github.com/tomasbasham/gofp\"\n\nfunc FindUser(id string) gofp.Option[User] {\n    user, found := db.Get(id)\n    if !found {\n        return gofp.None[User]()\n    }\n    return gofp.Some(user)\n}\n\n// Chain operations without nil checks\nresult := FindUser(\"123\").\n    Map(func(u User) User { \n        u.LastAccessed = time.Now()\n        return u \n    }).\n    UnwrapOr(DefaultUser)\n```\n\n**Key functions:**\n- `Some(value)` - Create an Option containing a value\n- `None[T]()` - Create an empty Option\n- `Map(fn)` - Transform the contained value\n- `FlatMap(fn)` - Chain operations that return Options\n- `UnwrapOr(default)` - Extract value with fallback\n- `Filter(predicate)` - Convert Some to None if predicate fails\n\n### Result[T]\n\nRepresents a computation that may succeed with a value or fail with an\nerror. Provides structured error handling with stack traces and error wrapping.\n\n**Mathematical form:**\n\n```text\nResult T = Ok T | Err error\n```\n\n**When to use:**\n- Operations that can fail (file I/O, network calls, parsing)\n- Validation pipelines\n- Replacing multiple `if err != nil` checks\n- Building error contexts\n\n**Example:**\n\n```go\nfunc ProcessData(filename string) gofp.Result[Data] {\n    return ReadFile(filename).\n        FlatMap(ParseJSON).\n        FlatMap(ValidateSchema).\n        Map(Transform).\n        Wrap(\"failed to process data\")\n}\n\nfunc ReadFile(path string) gofp.Result[[]byte] {\n    data, err := os.ReadFile(path)\n    return gofp.FromReturn(data, err)\n}\n\n// Use the result\nresult := ProcessData(\"config.json\")\nif result.IsErr() {\n    log.Printf(\"Error: %v\\n%s\", result.UnwrapErr(), result.StackTrace())\n    return\n}\ndata := result.Unwrap()\n```\n\n**Key functions:**\n- `Ok(value)` - Create a successful Result\n- `Err[T](error)` - Create a failed Result\n- `FromReturn(value, err)` - Convert Go's `(T, error)` pattern\n- `Map(fn)` - Transform success values\n- `FlatMap(fn)` - Chain fallible operations\n- `Wrap(msg)` - Add error context\n- `Ensure(err, predicate)` - Validate and fail if predicate is false\n- `Recover(fn)` - Convert errors to values\n\n### Either[T, U]\n\nRepresents a value that can be one of two types. By convention, Left represents\nfailure and Right represents success, but both can hold any type.\n\n**Mathematical form:**\n\n```text\nEither T U = Left T | Right U\n```\n\n**When to use:**\n- Multiple error types that need different handling\n- Accumulating validation errors\n- Representing mutually exclusive outcomes\n- When Result's error type is too restrictive\n\n**Example:**\n\n```go\ntype ValidationError struct {\n    Field string\n    Message string\n}\n\nfunc ValidateAge(age int) gofp.Either[ValidationError, int] {\n    if age \u003c 0 {\n        return gofp.Left[ValidationError, int](ValidationError{\n            Field: \"age\",\n            Message: \"must be non-negative\",\n        })\n    }\n    return gofp.Right[ValidationError](age)\n}\n\n// Accumulate multiple validation errors\nfunc ValidateUser(user User) gofp.Either[[]ValidationError, User] {\n    validations := []gofp.Either[ValidationError, gofp.Unit]{\n        ValidateAge(user.Age).Map(func(int) gofp.Unit { \n            return gofp.UnitValue\n        }),\n        ValidateEmail(user.Email).Map(func(string) gofp.Unit {\n            return gofp.UnitValue\n        }),\n    }\n    // Use EitherSequence to collect all errors or succeed\n    // Implementation depends on your error handling strategy\n}\n```\n\n**Key functions:**\n- `Left[T, U](value)` - Create a Left value\n- `Right[T, U](value)` - Create a Right value\n- `FromResult(result)` - Convert Result to Either\n- `Map(fn)` - Transform Right values\n- `MapLeft(fn)` - Transform Left values\n- `FlatMap(fn)` - Chain Either-returning operations\n- `Swap()` - Exchange Left and Right\n- `EitherFold(leftFn, rightFn)` - Handle both cases\n\n### Reader[E, A]\n\nRepresents a computation that reads from a shared environment. Provides\ndependency injection without explicit parameter passing.\n\n**Mathematical form:**\n\n```text\nReader E A = E -\u003e A\n```\n\n**When to use:**\n- Dependency injection\n- Configuration that flows through many functions\n- Testing with different environments\n- Avoiding global state\n\n**Example:**\n\n```go\nimport \"github.com/tomasbasham/gofp/reader\"\n\ntype Config struct {\n    Database string\n    APIKey   string\n    Debug    bool\n}\n\nfunc GetConnection() reader.Reader[Config, *sql.DB] {\n    return reader.Map(\n        reader.Ask[Config](),\n        func(cfg Config) *sql.DB {\n            db, _ := sql.Open(\"postgres\", cfg.Database)\n            return db\n        },\n    )\n}\n\nfunc FetchUsers() reader.Reader[Config, []User] {\n    return reader.FlatMap(\n        GetConnection(),\n        func(db *sql.DB) reader.Reader[Config, []User] {\n            return reader.Pure[Config](queryUsers(db))\n        },\n    )\n}\n\n// Execute with configuration\nconfig := Config{Database: \"postgres://...\", Debug: true}\nusers := FetchUsers().Run(config)\n```\n\n**Key functions:**\n- `Pure[E, A](value)` - Lift value into Reader\n- `Ask[E]()` - Access the environment\n- `Map(fn)` - Transform the result\n- `FlatMap(fn)` - Chain Reader operations\n- `Local(reader, fn)` - Temporarily modify environment\n\n### State[S, A]\n\nRepresents a computation that maintains and transforms state. Provides pure\nfunctional state management without mutable variables.\n\n**Mathematical form:**\n\n```text\nState S A = S -\u003e (A, S)\n```\n\n**When to use:**\n- Parser combinators\n- State machines\n- Game loops\n- Any computation requiring sequential state updates\n\n**Example:**\n\n```go\nimport \"github.com/tomasbasham/gofp/state\"\n\ntype GameState struct {\n    Score  int\n    Lives  int\n    Level  int\n}\n\nfunc AddPoints(points int) state.State[GameState, gofp.Unit] {\n    return state.Modify(func(s GameState) GameState {\n        s.Score += points\n        return s\n    })\n}\n\nfunc LoseLife() state.State[GameState, bool] {\n    return state.FlatMap(\n        state.Modify(func(s GameState) GameState {\n            s.Lives--\n            return s\n        }),\n        func(_ gofp.Unit) state.State[GameState, bool] {\n            return state.Gets(func(s GameState) bool {\n                return s.Lives \u003e 0\n            })\n        },\n    )\n}\n\n// Compose state operations\ngameLoop := AddPoints(100).\n    FlatMap(func(_ gofp.Unit) state.State[GameState, bool] {\n        return LoseLife()\n    })\n\ninitialState := GameState{Score: 0, Lives: 3, Level: 1}\nstillAlive, finalState := gameLoop.Run(initialState)\n```\n\n**Key functions:**\n- `Pure[S, A](value)` - Lift value without changing state\n- `Get[S]()` - Access current state\n- `Gets(fn)` - Extract value from state\n- `Put(state)` - Replace state\n- `Modify(fn)` - Transform state\n- `Map(fn)` - Transform the result\n- `FlatMap(fn)` - Chain state operations\n\n### Writer[W, A]\n\nRepresents a computation that accumulates output (logs, events, metrics)\nalongside producing a value. Requires a Monoid instance for combining outputs.\n\n**Mathematical form:**\n\n```text\nWriter W A = () -\u003e (A, W) where W is a Monoid\n```\n\n**When to use:**\n- Collecting logs during computation\n- Audit trails\n- Gathering metrics\n- Accumulating warnings\n\n**Example:**\n\n```go\nimport \"github.com/tomasbasham/gofp/writer\"\n\n// Define a Monoid for combining string slices.\ntype SliceMonoid[T any] struct{}\n\nfunc (SliceMonoid[T]) Empty() []T {\n    return []T{}\n}\n\nfunc (SliceMonoid[T]) Append(a, b []T) []T {\n    return append(a, b...)\n}\n\nfunc ProcessItem(item string) writer.Writer[[]string, int] {\n    return writer.TellWithValue(\n        len(item),\n        []string{fmt.Sprintf(\"processed: %s\", item)},\n        SliceMonoid[string]{},\n    )\n}\n\nfunc ProcessBatch(items []string) writer.Writer[[]string, int] {\n    total := writer.Pure(0, SliceMonoid[string]{})\n    for _, item := range items {\n        total = writer.FlatMap(total, func(s int) writer.Writer[[]string, int] {\n            return writer.Map(ProcessItem(item), func(length int) int { \n                return s + length\n            })\n        })\n    }\n    return total\n}\n\nresult, logs := ProcessBatch([]string{\"hello\", \"world\"}).Run()\n// result = 10\n// logs = [\"processed: hello\", \"processed: world\"]\n```\n\n**Key functions:**\n- `Pure(value, monoid)` - Create Writer without output\n- `Tell(output, monoid)` - Create Writer with only output\n- `TellWithValue(value, output, monoid)` - Create Writer with both\n- `Map(fn)` - Transform the value\n- `FlatMap(fn)` - Chain Writer operations\n- `Listen(writer)` - Include output in the value\n\n### Monoids\n\nA Monoid is a mathematical structure that defines how to combine values of the\nsame type. It consists of:\n\n1. **An identity element (empty)** - A value that, when combined with any other\n   value, returns that value unchanged\n1. **An associative binary operation (append)** - A way to combine two values\n   that satisfies: `append(append(a, b), c) = append(a, append(b, c))`\n\nIn this module, Monoids are represented by an interface:\n\n```go\ntype Monoid[A any] interface {\n    Empty() A \n    Append(a, b A) A\n}\n```\n\nThe Writer monad uses Monoids to combine outputs from multiple computations. For\nexample, if you're accumulating log messages (strings), you'd use a string\nconcatenation Monoid. If you're collecting events (slices), you'd use a slice\nconcatenation Monoid.\n\n## Common Patterns\n\n### Sequencing Operations\n\nAll monads provide `Sequence` functions to transform slices of monadic values:\n\n```go\n// Options: returns None if any element is None\noptions := []gofp.Option[int]{gofp.Some(1), gofp.Some(2), gofp.Some(3)}\nresult := gofp.OptionSequence(options) // Some([]int{1, 2, 3})\n\n// Results: returns Err if any operation fails\nresults := []gofp.Result[int]{gofp.Ok(1), gofp.Ok(2), gofp.Ok(3)}\ncombined := gofp.ResultSequence(results) // Ok([]int{1, 2, 3})\n\n// Eithers: returns Left if any element is Left\neithers := []gofp.Either[string, int]{\n    gofp.Right[string](1),\n    gofp.Right[string](2),\n}\nsequenced := gofp.EitherSequence(eithers) // Right([]int{1, 2})\n```\n\n### Folding\n\nExtract values by handling both success and failure cases:\n\n```go\n// Option\nvalue := gofp.OptionFold(\n    maybeUser,\n    func() string { return \"No user found\" },\n    func(u User) string { return u.Name },\n)\n\n// Result\nmessage := gofp.ResultFold(\n    operation,\n    func(err error) string { return fmt.Sprintf(\"Error: %v\", err) },\n    func(val int) string { return fmt.Sprintf(\"Success: %d\", val) },\n)\n\n// Either\noutput := gofp.EitherFold(\n    validation,\n    func(err ValidationError) string { return err.Message },\n    func(data Data) string { return \"Valid\" },\n)\n```\n\n### Combining Values\n\nUse `Apply` functions to combine multiple monadic values:\n\n```go\n// Combine two Options\nadd := func(a int) func(int) int {\n    return func(b int) int { return a + b }\n}\n\nopt1 := gofp.Some(5)\nopt2 := gofp.Some(3)\noptFn := gofp.Some(add)\n\n// This is typically done with curried functions or helper combinators\nresult := gofp.OptionApply(opt1, gofp.OptionMap(opt2, add))\n```\n\n## When NOT to Use This Library\n\nFunctional programming patterns aren't always the right choice for Go\nprojects. Consider avoiding this library when:\n\n1. **Your team is unfamiliar with functional concepts** - The learning curve can\n   slow development and reduce code maintainability if the team doesn't\n   understand monads.\n1. **Simple error handling suffices** - For straightforward operations, Go's\n   standard `if err != nil` pattern is clearer and more idiomatic.\n1. **Performance is critical** - Monadic composition introduces additional\n   function calls and allocations. Benchmark first if you're in a hot path.\n1. **You're writing library code for the Go community** - Most Go developers\n   expect idiomatic Go patterns. Using monads in public APIs creates friction.\n1. **The problem domain is simple** - Don't add abstraction layers when direct,\n   imperative code would be clearer.\n1. **You can't justify the abstraction** - If you find yourself wrapping and\n   unwrapping frequently, or if the monadic code is harder to read than\n   imperative code, reconsider.\n\n### Good Use Cases\n\n- **Complex error handling pipelines** with multiple failure points\n- **Configuration-heavy applications** where Reader monad reduces parameter\n  passing\n- **Parser combinators** where State monad shines\n- **Validation logic** that accumulates errors\n- **Code that benefits from explicit optionality** beyond nil checks\n\n### Anti-patterns\n\n```go\n// DON'T: Use Result for simple operations\nfunc Add(a, b int) gofp.Result[int] {\n    return gofp.Ok(a + b) // Unnecessary wrapper\n}\n\n// DO: Use Result when operations can genuinely fail\nfunc Divide(a, b int) gofp.Result[int] {\n    if b == 0 {\n        return gofp.Err[int](errors.New(\"division by zero\"))\n    }\n    return gofp.Ok(a / b)\n}\n\n// DON'T: Wrap every nullable value in Option\nfunc GetName(user *User) gofp.Option[string] {\n    if user == nil {\n        return gofp.None[string]()\n    }\n    return gofp.Some(user.Name) // Just check for nil normally\n}\n\n// DO: Use Option when optionality is semantically meaningful\nfunc GetMiddleName(user User) gofp.Option[string] {\n    // Middle name is genuinely optional in the domain\n    if user.MiddleName == \"\" {\n        return gofp.None[string]()\n    }\n    return gofp.Some(user.MiddleName)\n}\n```\n\n## License\n\nThis project is licensed under the [MIT License](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomasbasham%2Fgofp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftomasbasham%2Fgofp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomasbasham%2Fgofp/lists"}