{"id":15748282,"url":"https://github.com/binaryphile/fluentfp","last_synced_at":"2026-02-14T20:05:48.473Z","repository":{"id":248640929,"uuid":"827573711","full_name":"binaryphile/fluentfp","owner":"binaryphile","description":"simple, readable fluent slices and options in Go","archived":false,"fork":false,"pushed_at":"2026-02-12T07:36:23.000Z","size":708,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-12T16:49:48.031Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"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/binaryphile.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"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":"2024-07-11T23:37:32.000Z","updated_at":"2026-02-12T07:36:26.000Z","dependencies_parsed_at":"2024-07-18T07:46:13.548Z","dependency_job_id":"470bfcac-21b7-4937-9e69-098835f75a2e","html_url":"https://github.com/binaryphile/fluentfp","commit_stats":null,"previous_names":["binaryphile/functrunk","binaryphile/fluentfp"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/binaryphile/fluentfp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/binaryphile%2Ffluentfp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/binaryphile%2Ffluentfp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/binaryphile%2Ffluentfp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/binaryphile%2Ffluentfp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/binaryphile","download_url":"https://codeload.github.com/binaryphile/fluentfp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/binaryphile%2Ffluentfp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29454788,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-14T15:52:44.973Z","status":"ssl_error","status_checked_at":"2026-02-14T15:52:11.208Z","response_time":53,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: 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":[],"created_at":"2024-10-04T05:41:12.306Z","updated_at":"2026-02-14T20:05:48.468Z","avatar_url":"https://github.com/binaryphile.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# fluentfp\n\n**Fluent** functional programming for Go: fewer bugs, less code, predictable performance.\n\n\u003e **Summary:** Eliminate control structures, eliminate the bugs they enable.\n\u003e Mixed codebases see 26% complexity reduction; pure pipelines drop 95%.\n\u003e The win isn't lines saved—it's bugs that become unwritable.\n\nfluentfp is a small set of composable utilities for data transformation and type safety in Go.\n\nFluent operations chain method calls on a single line—no intermediate variables, no loop scaffolding.\n\nSee [pkg.go.dev](https://pkg.go.dev/github.com/binaryphile/fluentfp) for complete API documentation.\n\n## Quick Start\n\n```bash\ngo get github.com/binaryphile/fluentfp\n```\n\n```go\n// Before: loop mechanics interleaved with intent\nvar names []string\nfor _, u := range users {\n    if u.IsActive() {\n        names = append(names, u.Name)\n    }\n}\n\n// After: just intent\nnames := slice.From(users).KeepIf(User.IsActive).ToString(User.GetName)\n```\n\n## The Problem\n\nLoop mechanics create bugs regardless of developer skill:\n\n- **Accumulator errors**: forgot to increment, wrong variable\n- **Defer in loop**: resources pile up until function returns\n- **Index typos**: `i+i` instead of `i+1`\n- **Ignored errors**: `_ = fn()` silently continues when \"impossible\" errors occur\n\nC-style loops add off-by-one errors: `i \u003c= n` instead of `i \u003c n`.\n\nThese bugs compile, pass review, and look correct. They continue to appear in highly-reviewed, very public projects. If the construct allows an error, it will eventually happen.\n\n## The Solution\n\n*Correctness by construction*: design code so errors can't occur.\n\n| Bug Class | Why It Happens | fluentfp Elimination |\n|-----------|----------------|---------------------|\n| Accumulator error | Manual state tracking | `Fold` manages state |\n| Defer in loop | Loop body accumulates | No loop body |\n| Index typo | Manual index math | Predicates operate on values |\n| Off-by-one (C-style) | Manual bounds | Iterate collection, not indices |\n| Ignored error | `_ =` discards error | `must.BeNil` enforces invariant |\n\n## Measurable Impact\n\n| Codebase Type | Code Reduction | Complexity Reduction |\n|---------------|----------------|---------------------|\n| Mixed (typical) | 12% | 26% |\n| Pure pipeline | 47% | 95% |\n\n*Complexity measured via `scc` (cyclomatic complexity approximation). See [methodology](methodology.md#code-metrics-tool-scc).*\n\n## Performance\n\n| Operation | Loop | Chain | Result |\n|-----------|------|-------|--------|\n| Filter only | 5.6 μs | 5.5 μs | **Equal** |\n| Filter + Map | 3.1 μs | 7.6 μs | Loop 2.5× faster |\n| Count only | 0.26 μs | 7.6 μs | Loop 29× faster |\n\nSingle operations equal properly-written loops (both pre-allocate). In practice, many loops use naive append for simplicity—chains beat those. Multi-operation chains allocate per operation. See [full benchmarks](methodology.md#benchmark-results).\n\n## When to Use fluentfp\n\n**High yield** (adopt broadly):\n- Data pipelines, ETL, report generators\n- Filter/map/fold patterns\n- Field extraction from collections\n\n**Medium yield** (adopt selectively):\n- API handlers with data transformation\n- Config validation\n\n**Low yield** (probably skip):\n- I/O-heavy code with minimal transformation\n- Graph/tree traversal\n- Streaming/channel-based pipelines\n\n## When to Use Loops\n\n- **Channel consumption**: `for r := range ch`\n- **Complex control flow**: break, continue, early return\n- **Index-dependent logic**: when you need `i` for more than indexing\n\n## Parallelism Readiness\n\nPure functions + immutable data = safe parallelism.\n\n**Note:** fluentfp does not provide parallel operations. But the patterns it encourages—pure transforms, no shared state—are exactly what makes code *parallel-ready* when you need it.\n\n```go\n// With errgroup (idiomatic Go)\nimport \"golang.org/x/sync/errgroup\"\n\nvar g errgroup.Group\nresults := make([]Result, len(items))\nfor i, item := range items {\n    i, item := i, item  // capture by value for closure\n    g.Go(func() error {\n        results[i] = transform(item)  // Safe: transform is pure, i is unique\n        return nil\n    })\n}\ng.Wait()\n```\n\n**Benchmarked crossover (Go, 8 cores):**\n\n| N | Sequential | Parallel | Speedup | Verdict |\n|---|------------|----------|---------|---------|\n| 100 | 5.6μs | 9.3μs | 0.6× | Sequential wins |\n| 1,000 | 56μs | 40μs | 1.4× | Parallel starts winning |\n| 10,000 | 559μs | 200μs | 2.8× | Parallel wins |\n| 100,000 | 5.6ms | 1.4ms | 4.0× | Parallel wins decisively |\n\n**When to parallelize:**\n- N \u003e 1K items AND CPU-bound transform → yes\n- N \u003c 500 OR transform \u003c 100ns → no (overhead dominates)\n- I/O-bound (HTTP calls, disk) → yes (waiting is free to parallelize)\n\n**Key insight:** The discipline investment—writing pure transforms—pays off when you need parallelism and don't have to refactor first.\n\n*Reproduce these benchmarks: `go test -bench=. -benchmem ./examples/`*\n\n## Packages\n\n| Package | Purpose | Key Functions |\n|---------|---------|---------------|\n| [slice](slice/) | Collection transforms | `KeepIf`, `RemoveIf`, `Fold`, `ToString` |\n| [option](option/) | Nil safety | `Of`, `Get`, `Or`, `IfNotZero`, `IfNotNil` |\n| [either](either/) | Sum types | `Left`, `Right`, `Fold`, `Map` |\n| [must](must/) | Fallible funcs → HOF args | `Get`, `BeNil`, `Of` |\n| [value](value/) | Conditional value selection | `Of().When().Or()` |\n| [pair](tuple/pair/) | Zip slices | `Zip`, `ZipWith` |\n| [lof](lof/) | Lower-order function wrappers | `Len`, `Println`, `StringLen` |\n\n## Installation\n\n```bash\ngo get github.com/binaryphile/fluentfp\n```\n\n```go\nimport \"github.com/binaryphile/fluentfp/slice\"\nimport \"github.com/binaryphile/fluentfp/option\"\n```\n\n## Package Highlights\n\n### slice\n\nFluent collection operations with method chaining:\n\n```go\n// Filter and extract\nactives := slice.From(users).KeepIf(User.IsActive)\nnames := slice.From(users).ToString(User.GetName)\n\n// Map to arbitrary types\nusers := slice.MapTo[User](ids).Map(FetchUser)\n\n// Reduce\ntotal := slice.Fold(amounts, 0.0, sumFloat64)\n```\n\n### option\n\nEliminate nil panics with explicit optionality:\n\n```go\n// Create\nopt := option.Of(user)           // always ok\nopt := option.IfNotZero(name)    // ok if non-zero (comparable types)\nopt := option.IfNotNil(ptr)      // ok if not nil (pointer types)\n\n// Extract\nuser, ok := opt.Get()            // comma-ok\nuser := opt.Or(defaultUser)      // with fallback\n```\n\n### either\n\nSum types for values that are one of two possible types:\n\n```go\n// Create\nfail := either.Left[string, int](\"fail\")\nok42 := either.Right[string, int](42)\n\n// Extract with comma-ok\nif fortyTwo, ok := ok42.Get(); ok {\n    fmt.Println(fortyTwo) // 42\n}\n\n// Fold: handle both cases exhaustively\n// formatLeft returns an error message.\nformatLeft := func(err string) string { return \"Error: \" + err }\n// formatRight returns a success message.\nformatRight := func(n int) string { return fmt.Sprintf(\"Got: %d\", n) }\n\nmsg := either.Fold(ok42, formatLeft, formatRight)   // \"Got: 42\"\nmsg = either.Fold(fail, formatLeft, formatRight)    // \"Error: fail\"\n```\n\n### must\n\nMake error invariants explicit. Every `_ = fn()` should be `must.BeNil(fn())`:\n\n```go\n_ = os.Setenv(\"KEY\", value)           // Silent corruption if error\nmust.BeNil(os.Setenv(\"KEY\", value))   // Invariant enforced\n```\n\nAlso wraps fallible functions for HOF use:\n\n```go\nmustAtoi := must.Of(strconv.Atoi)\nints := slice.From(strings).ToInt(mustAtoi)\n```\n\n### value\n\nValue-first conditional selection:\n\n```go\n// \"value of CurrentTick when CurrentTick \u003c 7, or 7\"\ndays := value.Of(tick).When(tick \u003c 7).Or(7)\n\n// Lazy evaluation for expensive computations\nconfig := value.OfCall(loadFromDB).When(useCache).Or(defaultConfig)\n```\n\n## The Familiarity Discount\n\nA `for` loop you've seen 10,000 times feels instant to parse—but only because you've amortized the cognitive load through repetition. fluentfp expresses intent without mechanics; the simplicity is inherent, not learned. Be aware of this discount when comparing approaches.\n\n## Further Reading\n\n- [Full Analysis](analysis.md) - Technical deep-dive with examples\n- [Methodology](methodology.md) - How claims were measured\n- [Nil Safety](nil-safety.md) - The billion-dollar mistake and Go\n- [Naming Functions](naming-in-hof.md) - Function naming patterns for HOF use\n- [Library Comparison](comparison.md) - How fluentfp compares to alternatives\n\n## Recent Additions\n\n- **v0.14.0**: `value` package replaces `ternary` — value-first conditional selection\n- **v0.12.0**: **BREAKING** — `MapperTo.To` renamed to `MapperTo.Map` for clarity\n- **v0.8.0**: `either` package (Left/Right sum types), `ToInt32`/`ToInt64` (slice package)\n- **v0.7.0**: `IfNotZero` for comparable types (option package)\n- **v0.6.0**: `Fold`, `Unzip2/3/4`, `Zip`/`ZipWith` (pair package)\n- **v0.5.0**: `ToFloat64`, `ToFloat32`\n\n## License\n\nfluentfp is licensed under the MIT License. See [LICENSE](LICENSE) for more details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbinaryphile%2Ffluentfp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbinaryphile%2Ffluentfp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbinaryphile%2Ffluentfp/lists"}