mirror of
https://github.com/IBM/fp-go.git
synced 2025-11-23 22:14:53 +02:00
191 lines
6.9 KiB
Go
191 lines
6.9 KiB
Go
|
|
// Copyright (c) 2024 - 2025 IBM Corp.
|
||
|
|
// All rights reserved.
|
||
|
|
//
|
||
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
|
// you may not use this file except in compliance with the License.
|
||
|
|
// You may obtain a copy of the License at
|
||
|
|
//
|
||
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
|
//
|
||
|
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
|
// See the License for the specific language governing permissions and
|
||
|
|
// limitations under the License.
|
||
|
|
|
||
|
|
package either
|
||
|
|
|
||
|
|
import (
|
||
|
|
"github.com/IBM/fp-go/v2/internal/applicative"
|
||
|
|
S "github.com/IBM/fp-go/v2/semigroup"
|
||
|
|
)
|
||
|
|
|
||
|
|
// eitherApplicative is the internal implementation of the Applicative type class for Either.
|
||
|
|
// It provides the basic applicative operations: Of (lift), Map (transform), and Ap (apply).
|
||
|
|
type eitherApplicative[E, A, B any] struct {
|
||
|
|
fof func(a A) Either[E, A]
|
||
|
|
fmap func(func(A) B) Operator[E, A, B]
|
||
|
|
fap func(Either[E, A]) Operator[E, func(A) B, B]
|
||
|
|
}
|
||
|
|
|
||
|
|
// Of lifts a pure value into a Right context.
|
||
|
|
func (o *eitherApplicative[E, A, B]) Of(a A) Either[E, A] {
|
||
|
|
return o.fof(a)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Map applies a transformation function to the Right value, preserving Left values.
|
||
|
|
func (o *eitherApplicative[E, A, B]) Map(f func(A) B) Operator[E, A, B] {
|
||
|
|
return o.fmap(f)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Ap applies a wrapped function to a wrapped value.
|
||
|
|
// The behavior depends on which Ap implementation is used (fail-fast or validation).
|
||
|
|
func (o *eitherApplicative[E, A, B]) Ap(fa Either[E, A]) Operator[E, func(A) B, B] {
|
||
|
|
return o.fap(fa)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Applicative creates a standard Applicative instance for Either with fail-fast error handling.
|
||
|
|
//
|
||
|
|
// This returns a lawful Applicative that satisfies all applicative laws:
|
||
|
|
// - Identity: Ap(Of(identity))(v) == v
|
||
|
|
// - Homomorphism: Ap(Of(f))(Of(x)) == Of(f(x))
|
||
|
|
// - Interchange: Ap(Of(f))(u) == Ap(Map(f => f(y))(u))(Of(y))
|
||
|
|
// - Composition: Ap(Ap(Map(compose)(f))(g))(x) == Ap(f)(Ap(g)(x))
|
||
|
|
//
|
||
|
|
// The Applicative operations behave as follows:
|
||
|
|
// - Of: lifts a value into Right
|
||
|
|
// - Map: transforms Right values, preserves Left (standard functor)
|
||
|
|
// - Ap: fails fast - if either operand is Left, returns the first Left encountered
|
||
|
|
//
|
||
|
|
// This is the standard Either applicative that stops at the first error, making it
|
||
|
|
// suitable for computations where you want to short-circuit on failure.
|
||
|
|
//
|
||
|
|
// Example - Fail-Fast Behavior:
|
||
|
|
//
|
||
|
|
// app := either.Applicative[error, int, string]()
|
||
|
|
//
|
||
|
|
// // Both succeed - function application works
|
||
|
|
// value := either.Right[error](42)
|
||
|
|
// fn := either.Right[error](strconv.Itoa)
|
||
|
|
// result := app.Ap(value)(fn)
|
||
|
|
// // result is Right("42")
|
||
|
|
//
|
||
|
|
// // First error stops computation
|
||
|
|
// err1 := either.Left[func(int) string](errors.New("error 1"))
|
||
|
|
// err2 := either.Left[int](errors.New("error 2"))
|
||
|
|
// result2 := app.Ap(err2)(err1)
|
||
|
|
// // result2 is Left(error 1) - only first error is returned
|
||
|
|
//
|
||
|
|
// Type Parameters:
|
||
|
|
// - E: The error type (Left value)
|
||
|
|
// - A: The input value type (Right value)
|
||
|
|
// - B: The output value type after transformation
|
||
|
|
func Applicative[E, A, B any]() applicative.Applicative[A, B, Either[E, A], Either[E, B], Either[E, func(A) B]] {
|
||
|
|
return &eitherApplicative[E, A, B]{
|
||
|
|
Of[E, A],
|
||
|
|
Map[E, A, B],
|
||
|
|
Ap[B, E, A],
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ApplicativeV creates an Applicative with validation-style error accumulation.
|
||
|
|
//
|
||
|
|
// This returns a lawful Applicative that accumulates errors using a Semigroup when
|
||
|
|
// combining independent computations with Ap. This is the "validation" pattern commonly
|
||
|
|
// used for form validation, configuration validation, and parallel error collection.
|
||
|
|
//
|
||
|
|
// The returned instance satisfies all applicative laws:
|
||
|
|
// - Identity: Ap(Of(identity))(v) == v
|
||
|
|
// - Homomorphism: Ap(Of(f))(Of(x)) == Of(f(x))
|
||
|
|
// - Interchange: Ap(Of(f))(u) == Ap(Map(f => f(y))(u))(Of(y))
|
||
|
|
// - Composition: Ap(Ap(Map(compose)(f))(g))(x) == Ap(f)(Ap(g)(x))
|
||
|
|
//
|
||
|
|
// Key behaviors:
|
||
|
|
// - Of: lifts a value into Right
|
||
|
|
// - Map: transforms Right values, preserves Left (standard functor)
|
||
|
|
// - Ap: when both operands are Left, combines errors using the Semigroup
|
||
|
|
//
|
||
|
|
// Comparison with standard Applicative:
|
||
|
|
// - Applicative: Ap fails fast (returns first error)
|
||
|
|
// - ApplicativeV: Ap accumulates errors (combines all errors via Semigroup)
|
||
|
|
//
|
||
|
|
// Use cases:
|
||
|
|
// - Form validation: collect all validation errors at once
|
||
|
|
// - Configuration validation: report all configuration problems
|
||
|
|
// - Parallel independent checks: accumulate all failures
|
||
|
|
//
|
||
|
|
// Example - Error Accumulation for Form Validation:
|
||
|
|
//
|
||
|
|
// type ValidationErrors []string
|
||
|
|
//
|
||
|
|
// // Define how to combine error lists
|
||
|
|
// sg := semigroup.MakeSemigroup(func(a, b ValidationErrors) ValidationErrors {
|
||
|
|
// return append(append(ValidationErrors{}, a...), b...)
|
||
|
|
// })
|
||
|
|
//
|
||
|
|
// app := either.ApplicativeV[ValidationErrors, User, User](sg)
|
||
|
|
//
|
||
|
|
// // Validate multiple fields independently
|
||
|
|
// validateName := func(name string) Either[ValidationErrors, string] {
|
||
|
|
// if len(name) < 3 {
|
||
|
|
// return Left[string](ValidationErrors{"Name must be at least 3 characters"})
|
||
|
|
// }
|
||
|
|
// return Right[ValidationErrors](name)
|
||
|
|
// }
|
||
|
|
//
|
||
|
|
// validateAge := func(age int) Either[ValidationErrors, int] {
|
||
|
|
// if age < 18 {
|
||
|
|
// return Left[int](ValidationErrors{"Must be 18 or older"})
|
||
|
|
// }
|
||
|
|
// return Right[ValidationErrors](age)
|
||
|
|
// }
|
||
|
|
//
|
||
|
|
// validateEmail := func(email string) Either[ValidationErrors, string] {
|
||
|
|
// if !strings.Contains(email, "@") {
|
||
|
|
// return Left[string](ValidationErrors{"Invalid email format"})
|
||
|
|
// }
|
||
|
|
// return Right[ValidationErrors](email)
|
||
|
|
// }
|
||
|
|
//
|
||
|
|
// // Create a constructor function lifted into Either
|
||
|
|
// makeUser := func(name string) func(int) func(string) User {
|
||
|
|
// return func(age int) func(string) User {
|
||
|
|
// return func(email string) User {
|
||
|
|
// return User{Name: name, Age: age, Email: email}
|
||
|
|
// }
|
||
|
|
// }
|
||
|
|
// }
|
||
|
|
//
|
||
|
|
// // Apply validations - all errors are collected
|
||
|
|
// name := validateName("ab") // Left: name too short
|
||
|
|
// age := validateAge(16) // Left: age too low
|
||
|
|
// email := validateEmail("invalid") // Left: invalid email
|
||
|
|
//
|
||
|
|
// // Combine all validations using ApV
|
||
|
|
// result := app.Ap(name)(
|
||
|
|
// app.Ap(age)(
|
||
|
|
// app.Ap(email)(
|
||
|
|
// app.Of(makeUser),
|
||
|
|
// ),
|
||
|
|
// ),
|
||
|
|
// )
|
||
|
|
// // result is Left(ValidationErrors{
|
||
|
|
// // "Name must be at least 3 characters",
|
||
|
|
// // "Must be 18 or older",
|
||
|
|
// // "Invalid email format"
|
||
|
|
// // })
|
||
|
|
// // All three errors are collected!
|
||
|
|
//
|
||
|
|
// Type Parameters:
|
||
|
|
// - E: The error type that must have a Semigroup for combining errors
|
||
|
|
// - A: The input value type (Right value)
|
||
|
|
// - B: The output value type after transformation
|
||
|
|
// - sg: Semigroup instance for combining Left values when both operands of Ap are Left
|
||
|
|
func ApplicativeV[E, A, B any](sg S.Semigroup[E]) applicative.Applicative[A, B, Either[E, A], Either[E, B], Either[E, func(A) B]] {
|
||
|
|
return &eitherApplicative[E, A, B]{
|
||
|
|
Of[E, A],
|
||
|
|
Map[E, A, B],
|
||
|
|
ApV[B, A](sg),
|
||
|
|
}
|
||
|
|
}
|