1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-11-23 22:14:53 +02:00
Files
fp-go/v2/either/applicative.go
Dr. Carsten Leue 909d626019 fix: serveral performance improvements
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-11-18 10:58:24 +01:00

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),
}
}