fluentfp

module
v0.11.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 23, 2026 License: MIT

README

fluentfp

Pragmatic functional programming for Go: fewer bugs, less code, predictable performance.

Summary: Eliminate control structures, eliminate the bugs they enable. Mixed codebases see 26% complexity reduction; pure pipelines drop 95%. The win isn't lines saved—it's bugs that become unwritable.

fluentfp is a small set of composable utilities for data transformation and type safety in Go.

Quick Start

go get github.com/binaryphile/fluentfp
// Before: loop mechanics interleaved with intent
var names []string
for _, u := range users {
    if u.IsActive() {
        names = append(names, u.Name)
    }
}

// After: just intent
names := slice.From(users).KeepIf(User.IsActive).ToString(User.GetName)

The Problem

Loop mechanics create bugs regardless of developer skill:

  • Accumulator errors: forgot to increment, wrong variable
  • Defer in loop: resources pile up until function returns
  • Index typos: i+i instead of i+1
  • Ignored errors: _ = fn() silently continues when "impossible" errors occur

C-style loops add off-by-one errors: i <= n instead of i < n.

These 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.

The Solution

Correctness by construction: design code so errors can't occur.

Bug Class Why It Happens fluentfp Elimination
Accumulator error Manual state tracking Fold manages state
Defer in loop Loop body accumulates No loop body
Index typo Manual index math Predicates operate on values
Off-by-one (C-style) Manual bounds Iterate collection, not indices
Ignored error _ = discards error must.BeNil enforces invariant

Measurable Impact

Codebase Type Code Reduction Complexity Reduction
Mixed (typical) 12% 26%
Pure pipeline 47% 95%

Complexity measured via scc (cyclomatic complexity approximation). See methodology.

Performance

Operation Loop Chain Result
Filter only 5.6 μs 5.5 μs Equal
Filter + Map 3.1 μs 7.6 μs Loop 2.5× faster
Count only 0.26 μs 7.6 μs Loop 29× faster

Single 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.

When to Use fluentfp

High yield (adopt broadly):

  • Data pipelines, ETL, report generators
  • Filter/map/fold patterns
  • Field extraction from collections

Medium yield (adopt selectively):

  • API handlers with data transformation
  • Config validation

Low yield (probably skip):

  • I/O-heavy code with minimal transformation
  • Graph/tree traversal
  • Streaming/channel-based pipelines

When to Use Loops

  • Channel consumption: for r := range ch
  • Complex control flow: break, continue, early return
  • Index-dependent logic: when you need i for more than indexing

Packages

Package Purpose Key Functions
slice Collection transforms KeepIf, RemoveIf, Fold, ToString
option Nil safety Of, Get, Or, IfNotZero, IfNotNil
either Sum types Left, Right, Fold, Map
must Fallible funcs → HOF args Get, BeNil, Of
ternary Conditional expressions If().Then().Else()
pair Zip slices Zip, ZipWith
lof Lower-order function wrappers Len, Println, StringLen

Installation

go get github.com/binaryphile/fluentfp
import "github.com/binaryphile/fluentfp/slice"
import "github.com/binaryphile/fluentfp/option"

Package Highlights

slice

Fluent collection operations with method chaining:

// Filter and extract
actives := slice.From(users).KeepIf(User.IsActive)
names := slice.From(users).ToString(User.GetName)

// Map to arbitrary types
users := slice.MapTo[User](ids).To(FetchUser)

// Reduce
total := slice.Fold(amounts, 0.0, sumFloat64)
option

Eliminate nil panics with explicit optionality:

// Create
opt := option.Of(user)           // always ok
opt := option.IfNotZero(name)    // ok if non-zero (comparable types)
opt := option.IfNotNil(ptr)      // ok if not nil (pointer types)

// Extract
user, ok := opt.Get()            // comma-ok
user := opt.Or(defaultUser)      // with fallback
either

Sum types for values that are one of two possible types:

// Create
fail := either.Left[string, int]("error")
ok42 := either.Right[string, int](42)

// Extract with comma-ok
if fortyTwo, ok := ok42.Get(); ok { /* use fortyTwo */ }

// Pattern match with Fold
msg := either.Fold(result,
    func(err string) string { return "Error: " + err },
    func(val int) string { return fmt.Sprintf("Got: %d", val) },
)
must

Make error invariants explicit. Every _ = fn() should be must.BeNil(fn()):

_ = os.Setenv("KEY", value)           // Silent corruption if error
must.BeNil(os.Setenv("KEY", value))   // Invariant enforced

Also wraps fallible functions for HOF use:

mustAtoi := must.Of(strconv.Atoi)
ints := slice.From(strings).ToInt(mustAtoi)
ternary

Conditional expressions:

status := ternary.If[string](done).Then("complete").Else("pending")

The Familiarity Discount

A 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.

Further Reading

Recent Additions

  • v0.8.0: either package (Left/Right sum types), ToInt32/ToInt64 (slice package)
  • v0.7.0: IfNotZero for comparable types (option package)
  • v0.6.0: Fold, Unzip2/3/4, Zip/ZipWith (pair package)
  • v0.5.0: ToFloat64, ToFloat32

License

fluentfp is licensed under the MIT License. See LICENSE for more details.

Directories

Path Synopsis
Package either provides a sum type representing a value of one of two types.
Package either provides a sum type representing a value of one of two types.
examples
code-shape command
exp
Package lof provides lower-order functions for use by higher-order functions.
Package lof provides lower-order functions for use by higher-order functions.
Package must provides functions to panic if a condition is not met.
Package must provides functions to panic if a condition is not met.
Package option provides types and functions to work with optional values.
Package option provides types and functions to work with optional values.
Package slice provides fluent slice types that can chain functional collection operations.
Package slice provides fluent slice types that can chain functional collection operations.
Package ternary provides a fluent ternary type for Go.
Package ternary provides a fluent ternary type for Go.
tuple
pair
Package pair provides tuple types and functions for working with pairs of values.
Package pair provides tuple types and functions for working with pairs of values.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL