idiomatic

package
v2.0.0 Latest Latest
Warning

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

Go to latest
Published: Dec 18, 2025 License: Apache-2.0 Imports: 0 Imported by: 0

Documentation

Overview

Package idiomatic provides functional programming constructs optimized for idiomatic Go.

Overview

The idiomatic package reimagines functional programming patterns using Go's native tuple return values instead of wrapper structs. This approach provides better performance, lower memory overhead, and a more familiar API for Go developers while maintaining functional programming principles.

Key Differences from Standard Packages

Unlike the standard fp-go packages (option, either, result) which use struct wrappers, the idiomatic package uses Go's native tuple patterns:

Standard either:  Either[E, A]              (struct wrapper)
Idiomatic result: (A, error)                (native Go tuple)

Standard option:  Option[A]                 (struct wrapper)
Idiomatic option: (A, bool)                 (native Go tuple)

Performance Benefits

The idiomatic approach offers several performance advantages:

  • Zero allocation for creating values (no heap allocations)
  • Better CPU cache locality (no pointer indirection)
  • Native Go compiler optimizations for tuples
  • Reduced garbage collection pressure
  • Smaller memory footprint

Benchmarks show 2-10x performance improvements for common operations compared to struct-based implementations, especially for simple operations like Map, Chain, and Fold.

Design Philosophy

The idiomatic packages follow these design principles:

1. Native Go Idioms: Use Go's built-in patterns (tuples, error handling) 2. Zero-Cost Abstraction: No runtime overhead for functional patterns 3. Composability: All operations compose naturally with standard Go code 4. Familiarity: API feels natural to Go developers 5. Type Safety: Full compile-time type checking

Subpackages

The idiomatic package includes three main subpackages:

## idiomatic/option

Implements the Option monad using (value, bool) tuples where the boolean indicates presence (true) or absence (false). This is similar to Go's map lookup pattern.

Example usage:

import "github.com/IBM/fp-go/v2/idiomatic/option"

// Creating options
some := option.Some(42)        // (42, true)
none := option.None[int]()     // (0, false)

// Transforming values
double := option.Map(N.Mul(2))
result := double(some)         // (84, true)
result = double(none)          // (0, false)

// Chaining operations
validate := option.Chain(func(x int) (int, bool) {
    if x > 0 { return x * 2, true }
    return 0, false
})
result = validate(some)        // (84, true)

// Pattern matching
value := option.GetOrElse(func() int { return 0 })(some)  // 42

## idiomatic/result

Implements the Either/Result monad using (value, error) tuples, leveraging Go's standard error handling pattern. By convention, (value, nil) represents success and (zero, error) represents failure.

Example usage:

import "github.com/IBM/fp-go/v2/idiomatic/result"

// Creating results
success := result.Right(42)                        // (42, nil)
failure := result.Left[int](errors.New("oops"))    // (0, error)

// Transforming values
double := result.Map(N.Mul(2))
res := double(success)                             // (84, nil)
res = double(failure)                              // (0, error)

// Chaining operations (short-circuits on error)
validate := result.Chain(func(x int) (int, error) {
    if x > 0 { return x * 2, nil }
    return 0, errors.New("negative")
})
res = validate(success)                            // (84, nil)

// Pattern matching
output := result.Fold(
    func(err error) string { return "Error: " + err.Error() },
    func(n int) string { return fmt.Sprintf("Success: %d", n) },
)(success)  // "Success: 42"

// Direct integration with Go error handling
value, err := result.Right(42)
if err != nil {
    // handle error
}

## idiomatic/ioresult

Implements the IOResult monad using func() (value, error) for IO operations that can fail. This combines IO effects (side-effectful operations) with Go's standard error handling pattern. It's the idiomatic version of IOEither, representing computations that perform side effects and may fail.

Example usage:

import "github.com/IBM/fp-go/v2/idiomatic/ioresult"

// Creating IOResult values
success := ioresult.Of(42)                          // func() (int, error) returning (42, nil)
failure := ioresult.Left[int](errors.New("oops"))   // func() (int, error) returning (0, error)

// Reading a file with IOResult
readConfig := ioresult.FromIO(func() string {
    return "config.json"
})

// Transforming IO operations
processFile := F.Pipe2(
    readConfig,
    ioresult.Map(strings.ToUpper),
    ioresult.Chain(func(path string) ioresult.IOResult[[]byte] {
        return func() ([]byte, error) {
            return os.ReadFile(path)
        }
    }),
)

// Execute the IO operation
content, err := processFile()
if err != nil {
    log.Fatal(err)
}

// Resource management with Bracket
result, err := ioresult.Bracket(
    func() (*os.File, error) { return os.Open("data.txt") },
    func(f *os.File, err error) ioresult.IOResult[any] {
        return func() (any, error) { return nil, f.Close() }
    },
    func(f *os.File) ioresult.IOResult[[]byte] {
        return func() ([]byte, error) { return io.ReadAll(f) }
    },
)()

Key features:

  • Lazy evaluation: Operations are not executed until the IOResult is called
  • Composable: Chain IO operations that may fail
  • Error handling: Automatic error propagation and recovery
  • Resource safety: Bracket ensures proper resource cleanup
  • Parallel execution: ApPar and TraverseArrayPar for concurrent operations

Type Signatures

The idiomatic packages use function types that work naturally with Go tuples:

For option package:

Operator[A, B any] = func(A, bool) (B, bool)   // Transform Option[A] to Option[B]
Kleisli[A, B any]  = func(A) (B, bool)         // Monadic function from A to Option[B]

For result package:

Operator[A, B any] = func(A, error) (B, error) // Transform Result[A] to Result[B]
Kleisli[A, B any]  = func(A) (B, error)        // Monadic function from A to Result[B]

For ioresult package:

IOResult[A any]    = func() (A, error)             // IO operation returning A or error
Operator[A, B any] = func(IOResult[A]) IOResult[B] // Transform IOResult[A] to IOResult[B]
Kleisli[A, B any]  = func(A) IOResult[B]           // Monadic function from A to IOResult[B]

When to Use Idiomatic vs Standard Packages

Use idiomatic packages when:

  • Performance is critical (hot paths, tight loops)
  • You want zero-allocation functional patterns
  • You prefer Go's native error handling style
  • You're integrating with existing Go code that uses tuples
  • Memory efficiency matters (embedded systems, high-scale services)
  • You need IO operations with error handling (use ioresult)

Use standard packages when:

  • You need full algebraic data type semantics
  • You're porting code from other FP languages
  • You want explicit Either[E, A] with custom error types
  • You need the complete suite of FP abstractions
  • Code clarity outweighs performance concerns

Choosing Between result and ioresult

Use result when:

  • Operations are pure (same input always produces same output)
  • No side effects are involved (no IO, no state mutation)
  • You want to represent success/failure without execution delay

Use ioresult when:

  • Operations perform IO (file system, network, database)
  • Side effects are part of the computation
  • You need lazy evaluation (defer execution until needed)
  • You want to compose IO operations that may fail
  • Resource management is required (files, connections, locks)

Performance Comparison

Benchmark results comparing idiomatic vs standard packages (examples):

Operation          Standard      Idiomatic     Improvement
---------          --------      ---------     -----------
Right/Some         3.2 ns/op     0.5 ns/op     6.4x faster
Left/None          3.5 ns/op     0.5 ns/op     7.0x faster
Map (Right/Some)   5.8 ns/op     1.2 ns/op     4.8x faster
Map (Left/None)    3.8 ns/op     1.0 ns/op     3.8x faster
Chain (success)    8.2 ns/op     2.1 ns/op     3.9x faster
Fold               6.5 ns/op     1.8 ns/op     3.6x faster

Memory allocations:

Operation          Standard      Idiomatic
---------          --------      ---------
Right/Some         16 B/op       0 B/op
Map                16 B/op       0 B/op
Chain              32 B/op       0 B/op

Interoperability

The idiomatic packages provide conversion functions for working with standard packages:

// Converting between idiomatic.option and standard option
import (
    stdOption "github.com/IBM/fp-go/v2/option"
    "github.com/IBM/fp-go/v2/idiomatic/option"
)

// Standard to idiomatic (conceptually - check actual API)
stdOpt := stdOption.Some(42)
idiomaticOpt := stdOption.Unwrap(stdOpt)  // Returns (42, true)

// Idiomatic to standard (conceptually - check actual API)
value, ok := option.Some(42)
stdOpt = stdOption.FromTuple(value, ok)

// Converting between idiomatic.result and standard result
import (
    stdResult "github.com/IBM/fp-go/v2/result"
    "github.com/IBM/fp-go/v2/idiomatic/result"
)

// The conversion is straightforward with Unwrap/UnwrapError
stdRes := stdResult.Right[error](42)
value, err := stdResult.UnwrapError(stdRes)  // (42, nil)

// And back
stdRes = stdResult.TryCatchError(value, err)

Common Patterns

## Pipeline Composition

Build complex data transformations using function composition:

import (
    F "github.com/IBM/fp-go/v2/function"
    "github.com/IBM/fp-go/v2/idiomatic/result"
)

output, err := F.Pipe3(
    parseInput(input),
    result.Map(validate),
    result.Chain(process),
    result.Map(format),
)

## IO Pipeline with IOResult

Compose IO operations that may fail:

import (
    F "github.com/IBM/fp-go/v2/function"
    "github.com/IBM/fp-go/v2/idiomatic/ioresult"
)

// Define IO operations
readFile := func(path string) ioresult.IOResult[[]byte] {
    return func() ([]byte, error) {
        return os.ReadFile(path)
    }
}

parseJSON := func(data []byte) ioresult.IOResult[Config] {
    return func() (Config, error) {
        var cfg Config
        err := json.Unmarshal(data, &cfg)
        return cfg, err
    }
}

// Compose operations (not executed yet)
loadConfig := F.Pipe1(
    readFile("config.json"),
    ioresult.Chain(parseJSON),
    ioresult.Map(validateConfig),
)

// Execute the IO pipeline
config, err := loadConfig()
if err != nil {
    log.Fatal(err)
}

## Error Accumulation with Validation

The idiomatic/result package supports validation patterns for accumulating multiple errors:

import "github.com/IBM/fp-go/v2/idiomatic/result"

results := []error{
    validate1(input),
    validate2(input),
    validate3(input),
}
allErrors := result.ValidationErrors(results)

## Working with Collections

Transform arrays while handling errors or missing values:

import "github.com/IBM/fp-go/v2/idiomatic/option"

// Transform array, short-circuit on first None
input := []int{1, 2, 3}
output, ok := option.TraverseArray(func(x int) (int, bool) {
    if x > 0 { return x * 2, true }
    return 0, false
})(input)  // ([2, 4, 6], true)

import "github.com/IBM/fp-go/v2/idiomatic/result"

// Transform array, short-circuit on first error
output, err := result.TraverseArray(func(x int) (int, error) {
    if x > 0 { return x * 2, nil }
    return 0, errors.New("invalid")
})(input)  // ([2, 4, 6], nil)

Integration with Standard Library

The idiomatic packages integrate seamlessly with Go's standard library:

// File operations with result
readFile := result.Chain(func(path string) ([]byte, error) {
    return os.ReadFile(path)
})
content, err := readFile("config.json", nil)

// HTTP requests with result
import "github.com/IBM/fp-go/v2/idiomatic/result/http"

resp, err := http.MakeRequest(http.GET, "https://api.example.com/data")

// Database queries with option
findUser := func(id int) (User, bool) {
    user, err := db.QueryRow("SELECT * FROM users WHERE id = ?", id)
    if err != nil {
        return User{}, false
    }
    return user, true
}

// File operations with IOResult and resource safety
import "github.com/IBM/fp-go/v2/idiomatic/ioresult"

processFile := ioresult.Bracket(
    // Acquire resource
    func() (*os.File, error) {
        return os.Open("data.txt")
    },
    // Release resource (always called)
    func(f *os.File, err error) ioresult.IOResult[any] {
        return func() (any, error) {
            return nil, f.Close()
        }
    },
    // Use resource
    func(f *os.File) ioresult.IOResult[string] {
        return func() (string, error) {
            data, err := io.ReadAll(f)
            return string(data), err
        }
    },
)
content, err := processFile()

// System command execution with IOResult
import ioexec "github.com/IBM/fp-go/v2/idiomatic/ioresult/exec"

version := F.Pipe1(
    ioexec.Command("git")([]string{"version"})([]byte{}),
    ioresult.Map(func(output exec.CommandOutput) string {
        return string(exec.StdOut(output))
    }),
)
result, err := version()

Best Practices

1. Use descriptive error messages:

result.Left[User](fmt.Errorf("user %d not found", id))

2. Prefer composition over complex logic:

F.Pipe3(input,
    result.Map(step1),
    result.Chain(step2),
    result.Map(step3),
)

3. Use Fold for final value extraction:

output := result.Fold(
    func(err error) Response { return ErrorResponse(err) },
    func(data Data) Response { return SuccessResponse(data) },
)(result)

4. Leverage GetOrElse for defaults:

value := option.GetOrElse(func() Config { return defaultConfig })(maybeConfig)

5. Use FromPredicate for validation:

positiveInt := result.FromPredicate(
    func(x int) bool { return x > 0 },
    func(x int) error { return fmt.Errorf("%d is not positive", x) },
)

Testing

Testing code using idiomatic packages is straightforward:

func TestTransformation(t *testing.T) {
    input := 21
    result, err := F.Pipe2(
        input,
        result.Right[int],
        result.Map(N.Mul(2)),
    )
    assert.NoError(t, err)
    assert.Equal(t, 42, result)
}

func TestOptionHandling(t *testing.T) {
    value, ok := F.Pipe2(
        42,
        option.Some[int],
        option.Map(N.Mul(2)),
    )
    assert.True(t, ok)
    assert.Equal(t, 84, value)
}

Resources

For more information on functional programming patterns in Go:

  • fp-go documentation: https://github.com/IBM/fp-go
  • Standard option package: github.com/IBM/fp-go/v2/option
  • Standard either package: github.com/IBM/fp-go/v2/either
  • Standard result package: github.com/IBM/fp-go/v2/result
  • Standard ioeither package: github.com/IBM/fp-go/v2/ioeither

See the subpackage documentation for detailed API references:

  • idiomatic/option: Option monad using (value, bool) tuples
  • idiomatic/result: Result/Either monad using (value, error) tuples
  • idiomatic/ioresult: IOResult monad using func() (value, error) for IO operations

Directories

Path Synopsis
context
readerresult
Package readerresult provides a ReaderResult monad specialized for context.Context.
Package readerresult provides a ReaderResult monad specialized for context.Context.
Package ioresult provides functional programming combinators for working with IO operations that can fail with errors, following Go's idiomatic (value, error) tuple pattern.
Package ioresult provides functional programming combinators for working with IO operations that can fail with errors, following Go's idiomatic (value, error) tuple pattern.
exec
Package exec provides utilities for executing system commands with IOResult-based error handling.
Package exec provides utilities for executing system commands with IOResult-based error handling.
Package option implements the Option monad using idiomatic Go tuple signatures.
Package option implements the Option monad using idiomatic Go tuple signatures.
number
Package number provides Option-based utilities for number conversions.
Package number provides Option-based utilities for number conversions.
Package readerioresult provides a ReaderIOResult monad that combines Reader, IO, and Result monads.
Package readerioresult provides a ReaderIOResult monad that combines Reader, IO, and Result monads.
Package readerresult provides a ReaderResult monad that combines the Reader and Result monads.
Package readerresult provides a ReaderResult monad that combines the Reader and Result monads.
Package result provides an idiomatic Go approach to error handling using the (value, error) tuple pattern.
Package result provides an idiomatic Go approach to error handling using the (value, error) tuple pattern.
exec
Package exec provides utilities for executing system commands with Either-based error handling.
Package exec provides utilities for executing system commands with Either-based error handling.
http
Package http provides utilities for creating HTTP requests with Either-based error handling.
Package http provides utilities for creating HTTP requests with Either-based error handling.

Jump to

Keyboard shortcuts

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