decode

package
v2.2.12 Latest Latest
Warning

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

Go to latest
Published: Feb 5, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Decode

type Decode[I, A any] = Reader[I, Validation[A]]

Decode is a function that decodes input I to type A with validation. It combines the Reader pattern (for accessing input) with Validation (for error handling).

Type: func(I) Validation[A]

A Decode function:

  1. Takes raw input of type I (e.g., JSON, string, bytes)
  2. Attempts to decode/parse it into type A
  3. Returns a Validation[A] with either: - Success(A): Successfully decoded value - Failures(Errors): Validation errors describing what went wrong

This type is the foundation of the decode package, enabling composable, type-safe decoding with comprehensive error reporting.

Example:

// Decode a string to an integer
decodeInt := func(input string) Validation[int] {
    n, err := strconv.Atoi(input)
    if err != nil {
        return validation.Failures[int](validation.Errors{
            &validation.ValidationError{
                Value:    input,
                Messsage: "not a valid integer",
                Cause:    err,
            },
        })
    }
    return validation.Success(n)
}  // Decode[string, int]

result := decodeInt("42")  // Success(42)
result := decodeInt("abc") // Failures([...])

func Do added in v2.2.6

func Do[I, S any](
	empty S,
) Decode[I, S]

Do creates an empty context of type S to be used with the Bind operation. This is the starting point for building up a context using do-notation style.

Example:

type Result struct {
    x int
    y string
}
result := Do(Result{})

func Left added in v2.2.11

func Left[I, A any](err Errors) Decode[I, A]

Left creates a Decode that always fails with the given validation errors. This is the dual of Of - while Of lifts a success value, Left lifts failure errors into the Decode context.

Left is useful for:

  • Creating decoders that represent known failure states
  • Short-circuiting decode pipelines with specific errors
  • Building custom validation error responses
  • Testing error handling paths

The returned decoder ignores its input and always returns a validation failure containing the provided errors. This makes it the identity element for the Alt/OrElse operations when used as a fallback.

Type signature: func(Errors) Decode[I, A]

  • Takes validation errors
  • Returns a decoder that always fails with those errors
  • The decoder ignores its input of type I
  • The failure type A can be any type (phantom type)

Example - Creating a failing decoder:

failDecoder := decode.Left[string, int](validation.Errors{
    &validation.ValidationError{
        Value:    nil,
        Messsage: "operation not supported",
    },
})
result := failDecoder("any input") // Always fails with the error

Example - Short-circuiting with specific errors:

validateAge := func(age int) Decode[map[string]any, int] {
    if age < 0 {
        return decode.Left[map[string]any, int](validation.Errors{
            &validation.ValidationError{
                Value:    age,
                Context:  validation.Context{{Key: "age", Type: "int"}},
                Messsage: "age cannot be negative",
            },
        })
    }
    return decode.Of[map[string]any](age)
}

Example - Building error responses:

notFoundError := decode.Left[string, User](validation.Errors{
    &validation.ValidationError{
        Messsage: "user not found",
    },
})

decoder := decode.MonadAlt(
    tryFindUser,
    func() Decode[string, User] { return notFoundError },
)

Example - Testing error paths:

// Create a decoder that always fails for testing
alwaysFails := decode.Left[string, int](validation.Errors{
    &validation.ValidationError{Messsage: "test error"},
})

// Test error recovery logic
recovered := decode.OrElse(func(errs Errors) Decode[string, int] {
    return decode.Of[string](0) // recover with default
})(alwaysFails)

result := recovered("input") // Success(0)

func MonadAlt added in v2.2.11

func MonadAlt[I, A any](first Decode[I, A], second Lazy[Decode[I, A]]) Decode[I, A]

MonadAlt provides alternative/fallback decoding with error aggregation. This is the Alternative pattern's core operation that tries the first decoder, and if it fails, tries the second decoder as a fallback.

**Key behaviors**:

  • If first succeeds: returns the first result (second is never evaluated)
  • If first fails and second succeeds: returns the second result
  • If both fail: **aggregates errors from both decoders**

**Error Aggregation**: Unlike simple fallback patterns, when both decoders fail, MonadAlt combines ALL errors from both attempts using the Errors monoid. This ensures complete visibility into why all alternatives failed, which is crucial for debugging and providing comprehensive error messages to users.

The name "Alt" comes from the Alternative type class in functional programming, which represents computations with a notion of choice and failure.

Use cases:

  • Trying multiple decoding strategies for the same input
  • Providing fallback decoders when primary decoder fails
  • Building validation pipelines with multiple alternatives
  • Implementing "try this, or else try that" logic

Example - Simple fallback:

primaryDecoder := func(input string) Validation[int] {
    n, err := strconv.Atoi(input)
    if err != nil {
        return either.Left[int](validation.Errors{
            {Value: input, Messsage: "not a valid integer"},
        })
    }
    return validation.Of(n)
}

fallbackDecoder := func() Decode[string, int] {
    return func(input string) Validation[int] {
        // Try parsing as float and converting to int
        f, err := strconv.ParseFloat(input, 64)
        if err != nil {
            return either.Left[int](validation.Errors{
                {Value: input, Messsage: "not a valid number"},
            })
        }
        return validation.Of(int(f))
    }
}

decoder := MonadAlt(primaryDecoder, fallbackDecoder)
result1 := decoder("42")    // Success(42) - primary succeeds
result2 := decoder("42.5")  // Success(42) - fallback succeeds
result3 := decoder("abc")   // Failures with both errors aggregated

Example - Multiple alternatives:

decoder1 := parseAsJSON
decoder2 := func() Decode[string, Config] { return parseAsYAML }
decoder3 := func() Decode[string, Config] { return parseAsINI }

// Try JSON, then YAML, then INI
decoder := MonadAlt(MonadAlt(decoder1, decoder2), decoder3)
// If all fail, errors from all three attempts are aggregated

Example - Error aggregation:

failing1 := func(input string) Validation[int] {
    return either.Left[int](validation.Errors{
        {Messsage: "primary decoder failed"},
    })
}
failing2 := func() Decode[string, int] {
    return func(input string) Validation[int] {
        return either.Left[int](validation.Errors{
            {Messsage: "fallback decoder failed"},
        })
    }
}

decoder := MonadAlt(failing1, failing2)
result := decoder("input")
// Result contains BOTH errors: ["primary decoder failed", "fallback decoder failed"]

func MonadAp

func MonadAp[B, I, A any](fab Decode[I, func(A) B], fa Decode[I, A]) Decode[I, B]

MonadAp applies a decoder containing a function to a decoder containing a value. This is the applicative apply operation that enables parallel composition of decoders.

Example:

decoderFn := decode.Of[string](func(n int) string {
    return fmt.Sprintf("Number: %d", n)
})
decoderVal := decode.Of[string](42)
result := decode.MonadAp(decoderFn, decoderVal)

func MonadChain

func MonadChain[I, A, B any](fa Decode[I, A], f Kleisli[I, A, B]) Decode[I, B]

MonadChain sequences two decode operations, passing the result of the first to the second. This is the monadic bind operation that enables sequential composition of decoders.

Example:

decoder1 := decode.Of[string](42)
decoder2 := decode.MonadChain(decoder1, func(n int) Decode[string, string] {
    return decode.Of[string](fmt.Sprintf("Number: %d", n))
})

func MonadChainLeft added in v2.2.11

func MonadChainLeft[I, A any](fa Decode[I, A], f Kleisli[I, Errors, A]) Decode[I, A]

MonadChainLeft transforms the error channel of a decoder, enabling error recovery and context addition. This is the uncurried version of ChainLeft, taking both the decoder and the transformation function directly.

**Key behaviors**:

  • Success values pass through unchanged - the handler is never called
  • On failure, the handler receives the errors and can recover or add context
  • When the handler also fails, **both original and new errors are aggregated**
  • The handler returns a Decode[I, A], giving it access to the original input

**Error Aggregation**: Unlike standard Either operations, when the transformation function returns a failure, both the original errors AND the new errors are combined using the Errors monoid. This ensures no validation errors are lost.

This function is the direct, uncurried form of ChainLeft. Use ChainLeft when you need a curried operator for composition pipelines, and use MonadChainLeft when you have both the decoder and transformation function available at once.

Use cases:

  • Adding contextual information to validation errors
  • Recovering from specific error conditions
  • Transforming error messages while preserving original errors
  • Implementing conditional recovery based on error types

Example - Error recovery:

failingDecoder := func(input string) Validation[int] {
    return either.Left[int](validation.Errors{
        {Value: input, Messsage: "not found"},
    })
}

recoverFromNotFound := func(errs Errors) Decode[string, int] {
    for _, err := range errs {
        if err.Messsage == "not found" {
            return Of[string](0) // recover with default
        }
    }
    return func(input string) Validation[int] {
        return either.Left[int](errs)
    }
}

decoder := MonadChainLeft(failingDecoder, recoverFromNotFound)
result := decoder("input") // Success(0) - recovered from failure

Example - Adding context:

addContext := func(errs Errors) Decode[string, int] {
    return func(input string) Validation[int] {
        return either.Left[int](validation.Errors{
            {
                Context:  validation.Context{{Key: "user", Type: "User"}, {Key: "age", Type: "int"}},
                Messsage: "failed to decode user age",
            },
        })
    }
}

decoder := MonadChainLeft(failingDecoder, addContext)
result := decoder("abc")
// Result will contain BOTH original error and context error

Example - Comparison with ChainLeft:

// MonadChainLeft - direct application
result1 := MonadChainLeft(decoder, handler)("input")

// ChainLeft - curried for pipelines
result2 := ChainLeft(handler)(decoder)("input")

// Both produce identical results

func MonadMap

func MonadMap[I, A, B any](fa Decode[I, A], f func(A) B) Decode[I, B]

MonadMap transforms the decoded value using the provided function. This is the functor map operation that applies a transformation to successful decode results.

Example:

decoder := decode.Of[string](42)
mapped := decode.MonadMap(decoder, func(n int) string {
    return fmt.Sprintf("Number: %d", n)
})

func Of

func Of[I, A any](a A) Decode[I, A]

Of creates a Decode that always succeeds with the given value. This is the pointed functor operation that lifts a pure value into the Decode context.

Example:

decoder := decode.Of[string](42)
result := decoder("any input") // Always returns validation.Success(42)

type Endomorphism added in v2.2.6

type Endomorphism[A any] = endomorphism.Endomorphism[A]

Endomorphism represents a function from a type to itself: func(A) A. This is an alias for endomorphism.Endomorphism[A].

In the decode context, endomorphisms are used with LetL to transform decoded values using pure functions that don't change the type.

Endomorphisms are useful for:

  • Normalizing data (e.g., trimming strings, rounding numbers)
  • Applying business rules (e.g., clamping values to ranges)
  • Data sanitization (e.g., removing special characters)

Example:

// Normalize a string by trimming and lowercasing
normalize := func(s string) string {
    return strings.ToLower(strings.TrimSpace(s))
}  // Endomorphism[string]

// Clamp an integer to a range
clamp := func(n int) int {
    if n < 0 { return 0 }
    if n > 100 { return 100 }
    return n
}  // Endomorphism[int]

// Use with LetL to transform decoded values
decoder := F.Pipe1(
    decodeString,
    LetL(nameLens, normalize),
)

type Errors added in v2.2.8

type Errors = validation.Errors

Errors is a collection of validation errors that occurred during decoding. This is an alias for validation.Errors, which is []*ValidationError.

Errors accumulates multiple validation failures, allowing decoders to report all problems at once rather than failing on the first error. This is particularly useful for form validation, API request validation, and configuration parsing where users benefit from seeing all issues simultaneously.

The Errors type forms a Semigroup and Monoid, enabling:

  • Concatenation: Combining errors from multiple decoders
  • Accumulation: Collecting errors through applicative operations
  • Empty value: An empty slice representing no errors (success)

Each error in the collection is a *ValidationError containing:

  • Value: The actual value that failed validation
  • Context: The path to the value in nested structures
  • Message: Human-readable error description
  • Cause: Optional underlying error

Example:

// Multiple validation failures
errors := Errors{
    &validation.ValidationError{
        Value:    "",
        Context:  []validation.ContextEntry{{Key: "name"}},
        Messsage: "name is required",
    },
    &validation.ValidationError{
        Value:    "invalid@",
        Context:  []validation.ContextEntry{{Key: "email"}},
        Messsage: "invalid email format",
    },
}

// Create a failed validation with these errors
result := validation.Failures[User](errors)

// Errors can be combined using the monoid
moreErrors := Errors{
    &validation.ValidationError{
        Value:    -1,
        Context:  []validation.ContextEntry{{Key: "age"}},
        Messsage: "age must be positive",
    },
}
allErrors := append(errors, moreErrors...)

type Kleisli

type Kleisli[I, A, B any] = Reader[A, Decode[I, B]]

Kleisli represents a function from A to a decoded B given input type I. It's a Reader that takes an input A and produces a Decode[I, B] function. This enables composition of decoding operations in a functional style.

Type: func(A) Decode[I, B]

which expands to: func(A) func(I) Validation[B]

Kleisli arrows are the fundamental building blocks for composing decoders. They allow you to chain decoding operations where each step can:

  1. Depend on the result of the previous step (the A parameter)
  2. Access the original input (the I parameter via Decode)
  3. Fail with validation errors (via Validation[B])

This is particularly useful for:

  • Conditional decoding based on previously decoded values
  • Multi-stage decoding pipelines
  • Dependent field validation

Example:

// Decode a user, then decode their age based on their type
decodeAge := func(userType string) Decode[map[string]any, int] {
    return func(data map[string]any) Validation[int] {
        if userType == "admin" {
            // Admins must be 18+
            age := data["age"].(int)
            if age < 18 {
                return validation.Failures[int](/* error */)
            }
            return validation.Success(age)
        }
        // Regular users can be any age
        return validation.Success(data["age"].(int))
    }
}  // Kleisli[map[string]any, string, int]

// Use with Chain to compose decoders
decoder := F.Pipe2(
    decodeUserType,           // Decode[map[string]any, string]
    Chain(decodeAge),         // Chains with Kleisli
    Map(func(age int) User {  // Transform to final type
        return User{Age: age}
    }),
)

type Lazy added in v2.2.10

type Lazy[A any] = lazy.Lazy[A]

Lazy represents a deferred computation that produces a value of type A. This is an alias for lazy.Lazy[A], which is func() A.

In the decode context, Lazy is used to defer expensive computations or recursive decoder definitions until they are actually needed. This is particularly important for:

  • Recursive data structures (e.g., trees, linked lists)
  • Expensive default values
  • Breaking circular dependencies in decoder definitions

A Lazy[A] is simply a function that takes no arguments and returns A. The computation is only executed when the function is called, allowing for lazy evaluation and recursive definitions.

Example:

// Define a recursive decoder for a tree structure
type Tree struct {
    Value    int
    Children []Tree
}

// Use Lazy to break the circular dependency
var decodeTree Decode[map[string]any, Tree]
decodeTree = func(data map[string]any) Validation[Tree] {
    // Lazy evaluation allows referencing decodeTree within itself
    childrenDecoder := Array(Lazy(func() Decode[map[string]any, Tree] {
        return decodeTree
    }))
    // ... rest of decoder implementation
}

// Lazy default value that's only computed if needed
expensiveDefault := Lazy(func() Config {
    // This computation only runs if the decode fails
    return computeExpensiveDefaultConfig()
})

type Monoid added in v2.2.10

type Monoid[A any] = monoid.Monoid[A]

Monoid represents an algebraic structure with an associative binary operation and an identity element. This is an alias for monoid.Monoid[A].

A Monoid[A] consists of:

  • Concat: func(A, A) A - An associative binary operation
  • Empty: func() A - An identity element

In the decode context, monoids are used to combine multiple decoders or validation results. The most common use case is combining validation errors from multiple decoders using the Errors monoid.

Properties:

  • Associativity: Concat(Concat(a, b), c) == Concat(a, Concat(b, c))
  • Identity: Concat(Empty(), a) == a == Concat(a, Empty())

Common monoid instances:

  • Errors: Combines validation errors from multiple sources
  • Array: Concatenates arrays of decoded values
  • String: Concatenates strings

Example:

// Combine validation errors from multiple decoders
errorsMonoid := validation.GetMonoid[int]()

// Decode multiple fields and combine errors
result1 := decodeField1(data)  // Validation[string]
result2 := decodeField2(data)  // Validation[int]

// If both fail, errors are combined using the monoid
combined := errorsMonoid.Concat(result1, result2)

// The monoid's Empty() provides a successful validation with no errors
empty := errorsMonoid.Empty()  // Success with no value

func AltMonoid added in v2.2.11

func AltMonoid[I, A any](zero Lazy[Decode[I, A]]) Monoid[Decode[I, A]]

AltMonoid creates a Monoid instance for Decode[I, A] using the Alt (alternative) operation. This monoid provides a way to combine decoders with fallback behavior, where the second decoder is used as an alternative if the first one fails.

The Alt operation implements the "try first, fallback to second" pattern, which is useful for decoding scenarios where you want to attempt multiple decoding strategies in sequence and use the first one that succeeds.

**Behavior**:

  • Empty: Returns the provided zero value (a lazy computation that produces a Decode[I, A])
  • Concat: Combines two decoders using Alt semantics:
  • If first succeeds: returns the first result (second is never evaluated)
  • If first fails: tries the second decoder as fallback
  • If both fail: **aggregates errors from both decoders**

**Error Aggregation**: When both decoders fail, all validation errors from both attempts are combined using the Errors monoid. This ensures complete visibility into why all alternatives failed.

This is different from AlternativeMonoid in that:

  • AltMonoid uses a custom zero value (provided by the user)
  • AlternativeMonoid derives the zero from an inner monoid
  • AltMonoid is simpler and only provides fallback behavior
  • AlternativeMonoid combines applicative and alternative behaviors

Type Parameters:

  • I: The input type being decoded
  • A: The output type after successful decoding

Parameters:

  • zero: A lazy computation that produces the identity/empty Decode[I, A]. This is typically a decoder that always succeeds with a default value, or could be a decoder that always fails representing "no decoding attempted"

Returns:

A Monoid[Decode[I, A]] that combines decoders with fallback behavior

Example - Using default value as zero:

m := AltMonoid(func() Decode[string, int] {
    return Of[string](0)
})

failing := func(input string) Validation[int] {
    return either.Left[int](validation.Errors{
        {Value: input, Messsage: "failed"},
    })
}
succeeding := func(input string) Validation[int] {
    return validation.Of(42)
}

combined := m.Concat(failing, succeeding)
result := combined("input")
// Result: Success(42) - falls back to second decoder

empty := m.Empty()
result2 := empty("input")
// Result: Success(0) - the provided zero value

Example - Chaining multiple fallbacks:

m := AltMonoid(func() Decode[string, Config] {
    return Of[string](defaultConfig)
})

primary := parseFromPrimarySource    // Fails
secondary := parseFromSecondarySource // Fails
tertiary := parseFromTertiarySource   // Succeeds

// Chain fallbacks
decoder := m.Concat(m.Concat(primary, secondary), tertiary)
result := decoder("input")
// Result: Success from tertiary - uses first successful decoder

Example - Error aggregation when all fail:

m := AltMonoid(func() Decode[string, int] {
    return func(input string) Validation[int] {
        return either.Left[int](validation.Errors{
            {Messsage: "no default available"},
        })
    }
})

failing1 := func(input string) Validation[int] {
    return either.Left[int](validation.Errors{
        {Value: input, Messsage: "error 1"},
    })
}
failing2 := func(input string) Validation[int] {
    return either.Left[int](validation.Errors{
        {Value: input, Messsage: "error 2"},
    })
}

combined := m.Concat(failing1, failing2)
result := combined("input")
// Result: Failures with accumulated errors: ["error 1", "error 2"]

Example - Building a decoder pipeline with fallbacks:

m := AltMonoid(func() Decode[string, Config] {
    return Of[string](defaultConfig)
})

// Try multiple decoding sources in order
decoders := []Decode[string, Config]{
    loadFromFile("config.json"),      // Try file first
    loadFromEnv,                       // Then environment
    loadFromRemote("api.example.com"), // Then remote API
}

// Fold using the monoid to get first successful config
result := array.MonoidFold(m)(decoders)
// Result: First successful config, or defaultConfig if all fail

Example - Comparing with AlternativeMonoid:

// AltMonoid - simple fallback with custom zero
altM := AltMonoid(func() Decode[string, int] {
    return Of[string](0)
})

// AlternativeMonoid - combines values when both succeed
import N "github.com/IBM/fp-go/v2/number"
altMonoid := AlternativeMonoid[string](N.MonoidSum[int]())

decoder1 := Of[string](10)
decoder2 := Of[string](32)

// AltMonoid: returns first success (10)
result1 := altM.Concat(decoder1, decoder2)("input")
// Result: Success(10)

// AlternativeMonoid: combines both successes (10 + 32 = 42)
result2 := altMonoid.Concat(decoder1, decoder2)("input")
// Result: Success(42)

func AlternativeMonoid added in v2.2.11

func AlternativeMonoid[I, A any](m Monoid[A]) Monoid[Decode[I, A]]

AlternativeMonoid creates a Monoid instance for Decode[I, A] using the Alternative pattern. This combines applicative error-accumulation behavior with alternative fallback behavior, allowing you to both accumulate errors and provide fallback alternatives when combining decoders.

The Alternative pattern provides two key operations:

  • Applicative operations (Of, Map, Ap): accumulate errors when combining decoders
  • Alternative operation (Alt): provide fallback when a decoder fails

This monoid is particularly useful when you want to:

  • Try multiple decoding strategies and fall back to alternatives
  • Combine successful values using the provided monoid
  • Accumulate all errors from failed attempts
  • Build decoding pipelines with fallback logic

**Behavior**:

  • Empty: Returns a decoder that always succeeds with the empty value from the inner monoid
  • Concat: Combines two decoders using both applicative and alternative semantics:
  • If first succeeds and second succeeds: combines decoded values using inner monoid
  • If first fails: tries second as fallback (alternative behavior)
  • If both fail: **accumulates all errors from both decoders**

**Error Aggregation**: When both decoders fail, all validation errors from both attempts are combined using the Errors monoid. This provides complete visibility into why all alternatives failed, which is essential for debugging and user feedback.

Type Parameters:

  • I: The input type being decoded
  • A: The output type after successful decoding

Parameters:

  • m: The monoid for combining successful decoded values of type A

Returns:

A Monoid[Decode[I, A]] that combines applicative and alternative behaviors

Example - Combining successful decoders:

import S "github.com/IBM/fp-go/v2/string"

m := AlternativeMonoid[string](S.Monoid)

decoder1 := func(input string) Validation[string] {
    return validation.Of("Hello")
}
decoder2 := func(input string) Validation[string] {
    return validation.Of(" World")
}

combined := m.Concat(decoder1, decoder2)
result := combined("input")
// Result: Success("Hello World") - values combined using string monoid

Example - Fallback behavior:

m := AlternativeMonoid[string](S.Monoid)

failing := func(input string) Validation[string] {
    return either.Left[string](validation.Errors{
        {Value: input, Messsage: "primary failed"},
    })
}
fallback := func(input string) Validation[string] {
    return validation.Of("fallback value")
}

combined := m.Concat(failing, fallback)
result := combined("input")
// Result: Success("fallback value") - second decoder used as fallback

Example - Error accumulation when both fail:

m := AlternativeMonoid[string](S.Monoid)

failing1 := func(input string) Validation[string] {
    return either.Left[string](validation.Errors{
        {Value: input, Messsage: "error 1"},
    })
}
failing2 := func(input string) Validation[string] {
    return either.Left[string](validation.Errors{
        {Value: input, Messsage: "error 2"},
    })
}

combined := m.Concat(failing1, failing2)
result := combined("input")
// Result: Failures with accumulated errors: ["error 1", "error 2"]

Example - Building decoder with multiple fallbacks:

import N "github.com/IBM/fp-go/v2/number"

m := AlternativeMonoid[string](N.MonoidSum[int]())

// Try to parse from different formats
parseJSON := func(input string) Validation[int] { /* ... */ }
parseYAML := func(input string) Validation[int] { /* ... */ }
parseINI := func(input string) Validation[int] { /* ... */ }

// Combine with fallback chain
decoder := m.Concat(m.Concat(parseJSON, parseYAML), parseINI)
// Uses first successful parser, or accumulates all errors if all fail

Example - Combining multiple configuration sources:

type Config struct{ Port int }
configMonoid := monoid.MakeMonoid(
    func(a, b Config) Config {
        if b.Port != 0 { return b }
        return a
    },
    Config{Port: 0},
)

m := AlternativeMonoid[map[string]any](configMonoid)

fromEnv := func(data map[string]any) Validation[Config] { /* ... */ }
fromFile := func(data map[string]any) Validation[Config] { /* ... */ }
fromDefault := func(data map[string]any) Validation[Config] {
    return validation.Of(Config{Port: 8080})
}

// Try env, then file, then default
decoder := m.Concat(m.Concat(fromEnv, fromFile), fromDefault)
// Returns first successful config, or all errors if all fail

func ApplicativeMonoid added in v2.2.10

func ApplicativeMonoid[I, A any](m Monoid[A]) Monoid[Decode[I, A]]

ApplicativeMonoid creates a Monoid instance for Decode[I, A] given a Monoid for A. This allows combining decoders where both the decoded values and validation errors are combined according to their respective monoid operations.

The resulting monoid enables:

  • Combining multiple decoders that produce monoidal values
  • Accumulating validation errors when any decoder fails
  • Building complex decoders from simpler ones through composition

**Behavior**:

  • Empty: Returns a decoder that always succeeds with the empty value from the inner monoid
  • Concat: Combines two decoders:
  • Both succeed: Combines decoded values using the inner monoid
  • Any fails: Accumulates all validation errors using the Errors monoid

This is particularly useful for:

  • Aggregating results from multiple independent decoders
  • Building decoders that combine partial results
  • Validating and combining configuration from multiple sources
  • Parallel validation with result accumulation

Example - Combining string decoders:

import S "github.com/IBM/fp-go/v2/string"

// Create a monoid for decoders that produce strings
m := ApplicativeMonoid[map[string]any](S.Monoid)

decoder1 := func(data map[string]any) Validation[string] {
    if name, ok := data["firstName"].(string); ok {
        return validation.Of(name)
    }
    return either.Left[string](validation.Errors{
        {Messsage: "missing firstName"},
    })
}

decoder2 := func(data map[string]any) Validation[string] {
    if name, ok := data["lastName"].(string); ok {
        return validation.Of(" " + name)
    }
    return either.Left[string](validation.Errors{
        {Messsage: "missing lastName"},
    })
}

// Combine decoders - will concatenate strings if both succeed
combined := m.Concat(decoder1, decoder2)
result := combined(map[string]any{
    "firstName": "John",
    "lastName":  "Doe",
}) // Success("John Doe")

Example - Error accumulation:

// If any decoder fails, errors are accumulated
result := combined(map[string]any{}) // Failures with both error messages

Example - Numeric aggregation:

import N "github.com/IBM/fp-go/v2/number"

intMonoid := monoid.MakeMonoid(N.Add[int], 0)
m := ApplicativeMonoid[string](intMonoid)

decoder1 := func(input string) Validation[int] {
    return validation.Of(10)
}
decoder2 := func(input string) Validation[int] {
    return validation.Of(32)
}

combined := m.Concat(decoder1, decoder2)
result := combined("input") // Success(42) - values are added

type Operator

type Operator[I, A, B any] = Kleisli[I, Decode[I, A], B]

Operator represents a decoding transformation that takes a decoded A and produces a decoded B. It's a specialized Kleisli arrow for composing decode operations where the input is already decoded. This allows chaining multiple decode transformations together.

Type: func(Decode[I, A]) Decode[I, B]

Operators are higher-order functions that transform one decoder into another. They are the result of partially applying functions like Map, Chain, and Ap, making them ideal for use in composition pipelines with F.Pipe.

Key characteristics:

  • Takes a Decode[I, A] as input
  • Returns a Decode[I, B] as output
  • Preserves the input type I (the raw data being decoded)
  • Transforms the output type from A to B

Common operators:

  • Map(f): Transforms successful decode results
  • Chain(f): Sequences dependent decode operations
  • Ap(fa): Applies function decoders to value decoders

Example:

// Create reusable operators
toString := Map(func(n int) string {
    return strconv.Itoa(n)
})  // Operator[string, int, string]

validatePositive := Chain(func(n int) Decode[string, int] {
    return func(input string) Validation[int] {
        if n <= 0 {
            return validation.Failures[int](/* error */)
        }
        return validation.Success(n)
    }
})  // Operator[string, int, int]

// Compose operators in a pipeline
decoder := F.Pipe2(
    decodeInt,          // Decode[string, int]
    validatePositive,   // Operator[string, int, int]
    toString,           // Operator[string, int, string]
)  // Decode[string, string]

result := decoder("42")  // Success("42")
result := decoder("-5")  // Failures([...])

func Alt added in v2.2.11

func Alt[I, A any](second Lazy[Decode[I, A]]) Operator[I, A, A]

Alt creates an operator that provides alternative/fallback decoding with error aggregation. This is the curried version of MonadAlt, useful for composition pipelines.

**Key behaviors** (identical to MonadAlt):

  • If first succeeds: returns the first result (second is never evaluated)
  • If first fails and second succeeds: returns the second result
  • If both fail: **aggregates errors from both decoders**

The Alt operator enables building reusable fallback chains that can be applied to different decoders. It reads naturally in pipelines: "apply this decoder, with this alternative if it fails."

Use cases:

  • Creating reusable fallback strategies
  • Building decoder combinators with alternatives
  • Composing multiple fallback layers
  • Implementing retry logic with different strategies

Example - Creating a reusable fallback:

// Create an operator that falls back to a default value
withDefault := Alt(func() Decode[string, int] {
    return Of[string](0)
})

// Apply to any decoder
decoder1 := withDefault(parseInteger)
decoder2 := withDefault(parseFromJSON)

result1 := decoder1("42")  // Success(42)
result2 := decoder1("abc") // Success(0) - fallback

Example - Composing multiple alternatives:

tryYAML := Alt(func() Decode[string, Config] { return parseAsYAML })
tryINI := Alt(func() Decode[string, Config] { return parseAsINI })
useDefault := Alt(func() Decode[string, Config] {
    return Of[string](defaultConfig)
})

// Build a pipeline: try JSON, then YAML, then INI, then default
decoder := useDefault(tryINI(tryYAML(parseAsJSON)))

Example - Error aggregation in pipeline:

failing1 := func(input string) Validation[int] {
    return either.Left[int](validation.Errors{{Messsage: "error 1"}})
}
failing2 := func() Decode[string, int] {
    return func(input string) Validation[int] {
        return either.Left[int](validation.Errors{{Messsage: "error 2"}})
    }
}
failing3 := func() Decode[string, int] {
    return func(input string) Validation[int] {
        return either.Left[int](validation.Errors{{Messsage: "error 3"}})
    }
}

// Chain multiple alternatives
decoder := Alt(failing3)(Alt(failing2)(failing1))
result := decoder("input")
// Result contains ALL errors: ["error 1", "error 2", "error 3"]

func Ap

func Ap[B, I, A any](fa Decode[I, A]) Operator[I, func(A) B, B]

Ap creates an operator that applies a function decoder to a value decoder. This is the curried version of MonadAp, useful for composition pipelines.

Example:

apOp := decode.Ap[string](decode.Of[string](42))
decoderFn := decode.Of[string](func(n int) string {
    return fmt.Sprintf("Number: %d", n)
})
result := apOp(decoderFn)

func ApS added in v2.2.6

func ApS[I, S1, S2, T any](
	setter func(T) func(S1) S2,
	fa Decode[I, T],
) Operator[I, S1, S2]

ApS attaches a value to a context S1 to produce a context S2 by considering the context and the value concurrently. This uses the applicative functor pattern, allowing parallel composition.

IMPORTANT: Unlike Bind which fails fast, ApS aggregates ALL validation errors from both the context and the value. If both validations fail, all errors are collected and returned together. This is useful for validating multiple independent fields and reporting all errors at once.

Example:

type State struct { x int; y int }
decoder := F.Pipe2(
    Do[string](State{}),
    ApS(func(x int) func(State) State {
        return func(s State) State { s.x = x; return s }
    }, Of[string](42)),
)
result := decoder("input") // Returns validation.Success(State{x: 42})

Error aggregation example:

// Both decoders fail - errors are aggregated
decoder1 := func(input string) Validation[State] {
    return validation.Failures[State](/* errors */)
}
decoder2 := func(input string) Validation[int] {
    return validation.Failures[int](/* errors */)
}
combined := ApS(setter, decoder2)(decoder1)
result := combined("input") // Contains BOTH sets of errors

func ApSL added in v2.2.6

func ApSL[I, S, T any](
	lens L.Lens[S, T],
	fa Decode[I, T],
) Operator[I, S, S]

ApSL attaches a value to a context using a lens-based setter. This is a convenience function that combines ApS with a lens, allowing you to use optics to update nested structures in a more composable way.

IMPORTANT: Like ApS, this function aggregates ALL validation errors. If both the context and the value fail validation, all errors are collected and returned together. This enables comprehensive error reporting for complex nested structures.

The lens parameter provides both the getter and setter for a field within the structure S. This eliminates the need to manually write setter functions.

Example:

type Address struct {
    Street string
    City   string
}

type Person struct {
    Name    string
    Address Address
}

// Create a lens for the Address field
addressLens := lens.MakeLens(
    func(p Person) Address { return p.Address },
    func(p Person, a Address) Person { p.Address = a; return p },
)

// Use ApSL to update the address
decoder := F.Pipe2(
    Of[string](Person{Name: "Alice"}),
    ApSL(
        addressLens,
        Of[string](Address{Street: "Main St", City: "NYC"}),
    ),
)
result := decoder("input") // Returns validation.Success(Person{...})

func Bind added in v2.2.6

func Bind[I, S1, S2, A any](
	setter func(A) func(S1) S2,
	f Kleisli[I, S1, A],
) Operator[I, S1, S2]

Bind attaches the result of a computation to a context S1 to produce a context S2. This is used in do-notation style to sequentially build up a context.

Example:

type State struct { x int; y int }
decoder := F.Pipe2(
    Do[string](State{}),
    Bind(func(x int) func(State) State {
        return func(s State) State { s.x = x; return s }
    }, func(s State) Decode[string, int] {
        return Of[string](42)
    }),
)
result := decoder("input") // Returns validation.Success(State{x: 42})

func BindL added in v2.2.6

func BindL[I, S, T any](
	lens L.Lens[S, T],
	f Kleisli[I, T, T],
) Operator[I, S, S]

BindL attaches the result of a computation to a context using a lens-based setter. This is a convenience function that combines Bind with a lens, allowing you to use optics to update nested structures based on their current values.

The lens parameter provides both the getter and setter for a field within the structure S. The computation function f receives the current value of the focused field and returns a Validation that produces the new value.

Unlike ApSL, BindL uses monadic sequencing, meaning the computation f can depend on the current value of the focused field.

Example:

type Counter struct {
    Value int
}

valueLens := lens.MakeLens(
    func(c Counter) int { return c.Value },
    func(c Counter, v int) Counter { c.Value = v; return c },
)

// Increment the counter, but fail if it would exceed 100
increment := func(v int) Decode[string, int] {
    return func(input string) Validation[int] {
        if v >= 100 {
            return validation.Failures[int](/* errors */)
        }
        return validation.Success(v + 1)
    }
}

decoder := F.Pipe1(
    Of[string](Counter{Value: 42}),
    BindL(valueLens, increment),
)
result := decoder("input") // Returns validation.Success(Counter{Value: 43})

func BindTo added in v2.2.6

func BindTo[I, S1, T any](
	setter func(T) S1,
) Operator[I, T, S1]

BindTo initializes a new state S1 from a value T. This is typically used as the first operation after creating a Decode value.

Example:

type State struct { value int }
decoder := F.Pipe1(
    Of[string](42),
    BindTo[string](func(x int) State { return State{value: x} }),
)
result := decoder("input") // Returns validation.Success(State{value: 42})

func Chain

func Chain[I, A, B any](f Kleisli[I, A, B]) Operator[I, A, B]

Chain creates an operator that sequences decode operations. This is the curried version of MonadChain, useful for composition pipelines.

Example:

chainOp := decode.Chain(func(n int) Decode[string, string] {
    return decode.Of[string](fmt.Sprintf("Number: %d", n))
})
decoder := chainOp(decode.Of[string](42))

func ChainLeft added in v2.2.8

func ChainLeft[I, A any](f Kleisli[I, Errors, A]) Operator[I, A, A]

ChainLeft transforms the error channel of a decoder, enabling error recovery and context addition. This is the left-biased monadic chain operation that operates on validation failures.

**Key behaviors**:

  • Success values pass through unchanged - the handler is never called
  • On failure, the handler receives the errors and can recover or add context
  • When the handler also fails, **both original and new errors are aggregated**
  • The handler returns a Decode[I, A], giving it access to the original input

**Error Aggregation**: Unlike standard Either operations, when the transformation function returns a failure, both the original errors AND the new errors are combined using the Errors monoid. This ensures no validation errors are lost.

Use cases:

  • Adding contextual information to validation errors
  • Recovering from specific error conditions
  • Transforming error messages while preserving original errors
  • Implementing conditional recovery based on error types

Example - Error recovery:

failingDecoder := func(input string) Validation[int] {
    return either.Left[int](validation.Errors{
        {Value: input, Messsage: "not found"},
    })
}

recoverFromNotFound := ChainLeft(func(errs Errors) Decode[string, int] {
    for _, err := range errs {
        if err.Messsage == "not found" {
            return Of[string](0) // recover with default
        }
    }
    return func(input string) Validation[int] {
        return either.Left[int](errs)
    }
})

decoder := recoverFromNotFound(failingDecoder)
result := decoder("input") // Success(0) - recovered from failure

Example - Adding context:

addContext := ChainLeft(func(errs Errors) Decode[string, int] {
    return func(input string) Validation[int] {
        return either.Left[int](validation.Errors{
            {
                Context:  validation.Context{{Key: "user", Type: "User"}, {Key: "age", Type: "int"}},
                Messsage: "failed to decode user age",
            },
        })
    }
})
// Result will contain BOTH original error and context error

func Let added in v2.2.6

func Let[I, S1, S2, B any](
	key func(B) func(S1) S2,
	f func(S1) B,
) Operator[I, S1, S2]

Let attaches the result of a pure computation to a context S1 to produce a context S2. Unlike Bind, the computation function returns a plain value, not wrapped in Decode.

Example:

type State struct { x int; computed int }
decoder := F.Pipe2(
    Do[string](State{x: 5}),
    Let[string](func(c int) func(State) State {
        return func(s State) State { s.computed = c; return s }
    }, func(s State) int { return s.x * 2 }),
)
result := decoder("input") // Returns validation.Success(State{x: 5, computed: 10})

func LetL added in v2.2.6

func LetL[I, S, T any](
	lens L.Lens[S, T],
	f Endomorphism[T],
) Operator[I, S, S]

LetL attaches the result of a pure computation to a context using a lens-based setter. This is a convenience function that combines Let with a lens, allowing you to use optics to update nested structures with pure transformations.

The lens parameter provides both the getter and setter for a field within the structure S. The transformation function f receives the current value of the focused field and returns the new value directly (not wrapped in Validation).

This is useful for pure transformations that cannot fail, such as mathematical operations, string manipulations, or other deterministic updates.

Example:

type Counter struct {
    Value int
}

valueLens := lens.MakeLens(
    func(c Counter) int { return c.Value },
    func(c Counter, v int) Counter { c.Value = v; return c },
)

// Double the counter value
double := func(v int) int { return v * 2 }

decoder := F.Pipe1(
    Of[string](Counter{Value: 21}),
    LetL(valueLens, double),
)
result := decoder("input") // Returns validation.Success(Counter{Value: 42})

func LetTo added in v2.2.6

func LetTo[I, S1, S2, B any](
	key func(B) func(S1) S2,
	b B,
) Operator[I, S1, S2]

LetTo attaches a constant value to a context S1 to produce a context S2.

Example:

type State struct { x int; name string }
result := F.Pipe2(
    Do(State{x: 5}),
    LetTo(func(n string) func(State) State {
        return func(s State) State { s.name = n; return s }
    }, "example"),
)

func LetToL added in v2.2.6

func LetToL[I, S, T any](
	lens L.Lens[S, T],
	b T,
) Operator[I, S, S]

LetToL attaches a constant value to a context using a lens-based setter. This is a convenience function that combines LetTo with a lens, allowing you to use optics to set nested fields to specific values.

The lens parameter provides the setter for a field within the structure S. Unlike LetL which transforms the current value, LetToL simply replaces it with the provided constant value b.

This is useful for resetting fields, initializing values, or setting fields to predetermined constants.

Example:

type Config struct {
    Debug   bool
    Timeout int
}

debugLens := lens.MakeLens(
    func(c Config) bool { return c.Debug },
    func(c Config, d bool) Config { c.Debug = d; return c },
)

decoder := F.Pipe1(
    Of[string](Config{Debug: true, Timeout: 30}),
    LetToL(debugLens, false),
)
result := decoder("input") // Returns validation.Success(Config{Debug: false, Timeout: 30})

func Map

func Map[I, A, B any](f func(A) B) Operator[I, A, B]

Map creates an operator that transforms decoded values. This is the curried version of MonadMap, useful for composition pipelines.

Example:

mapOp := decode.Map(func(n int) string {
    return fmt.Sprintf("Number: %d", n)
})
decoder := mapOp(decode.Of[string](42))

func OrElse added in v2.2.9

func OrElse[I, A any](f Kleisli[I, Errors, A]) Operator[I, A, A]

OrElse provides fallback decoding logic when the primary decoder fails. This is an alias for ChainLeft with a more semantic name for fallback scenarios.

**OrElse is exactly the same as ChainLeft** - they are aliases with identical implementations and behavior. The choice between them is purely about code readability and semantic intent:

  • Use **OrElse** when emphasizing fallback/alternative decoding logic
  • Use **ChainLeft** when emphasizing technical error channel transformation

**Key behaviors** (identical to ChainLeft):

  • Success values pass through unchanged - the handler is never called
  • On failure, the handler receives the errors and can provide an alternative
  • When the handler also fails, **both original and new errors are aggregated**
  • The handler returns a Decode[I, A], giving it access to the original input

The name "OrElse" reads naturally in code: "try this decoder, or else try this alternative." This makes it ideal for expressing fallback logic and default values.

Use cases:

  • Providing default values when decoding fails
  • Trying alternative decoding strategies
  • Implementing fallback chains with multiple alternatives
  • Input-dependent recovery (using access to original input)

Example - Simple fallback:

primaryDecoder := func(input string) Validation[int] {
    n, err := strconv.Atoi(input)
    if err != nil {
        return either.Left[int](validation.Errors{
            {Value: input, Messsage: "not a valid integer"},
        })
    }
    return validation.Of(n)
}

withDefault := OrElse(func(errs Errors) Decode[string, int] {
    return Of[string](0) // default to 0 if decoding fails
})

decoder := withDefault(primaryDecoder)
result1 := decoder("42")  // Success(42)
result2 := decoder("abc") // Success(0) - fallback

Example - Input-dependent fallback:

smartDefault := OrElse(func(errs Errors) Decode[string, int] {
    return func(input string) Validation[int] {
        // Access original input to determine appropriate default
        if strings.Contains(input, "http") {
            return validation.Of(80)
        }
        if strings.Contains(input, "https") {
            return validation.Of(443)
        }
        return validation.Of(8080)
    }
})

decoder := smartDefault(decodePort)
result1 := decoder("http-server")  // Success(80)
result2 := decoder("https-server") // Success(443)
result3 := decoder("other")        // Success(8080)

type Reader

type Reader[R, A any] = reader.Reader[R, A]

Reader represents a computation that depends on an environment R and produces a value A. This is an alias for reader.Reader[R, A], which is func(R) A.

In the decode context, Reader is used to access the input data being decoded. The environment R is typically the raw input (e.g., JSON, string, bytes) that needs to be decoded into a structured type A.

Example:

// A reader that extracts a field from a map
getField := func(data map[string]any) string {
    return data["name"].(string)
}  // Reader[map[string]any, string]

type Validation

type Validation[A any] = validation.Validation[A]

Validation represents the result of a validation operation that may contain validation errors or a successfully validated value of type A. This is an alias for validation.Validation[A], which is Either[Errors, A].

In the decode context:

  • Left(Errors): Decoding failed with one or more validation errors
  • Right(A): Successfully decoded value of type A

Example:

// Success case
valid := validation.Success(42)  // Right(42)

// Failure case
invalid := validation.Failures[int](validation.Errors{
    &validation.ValidationError{Messsage: "invalid format"},
})  // Left([...])

Jump to

Keyboard shortcuts

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