mirror of
https://github.com/IBM/fp-go.git
synced 2025-11-23 22:14:53 +02:00
145 lines
5.2 KiB
Go
145 lines
5.2 KiB
Go
// Copyright (c) 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 (
|
|
F "github.com/IBM/fp-go/v2/function"
|
|
S "github.com/IBM/fp-go/v2/semigroup"
|
|
)
|
|
|
|
// MonadApV is the applicative validation functor that combines errors using a semigroup.
|
|
//
|
|
// Unlike the standard [MonadAp] which short-circuits on the first Left (error),
|
|
// MonadApV accumulates all errors using the provided semigroup's Concat operation.
|
|
// This is particularly useful for validation scenarios where you want to collect
|
|
// all validation errors rather than stopping at the first one.
|
|
//
|
|
// The function takes a semigroup for combining errors and returns a function that
|
|
// applies a wrapped function to a wrapped value, accumulating errors if both are Left.
|
|
//
|
|
// Behavior:
|
|
// - If both fab and fa are Left, combines their errors using sg.Concat
|
|
// - If only fab is Left, returns Left with fab's error
|
|
// - If only fa is Left, returns Left with fa's error
|
|
// - If both are Right, applies the function and returns Right with the result
|
|
//
|
|
// Type Parameters:
|
|
// - B: The result type after applying the function
|
|
// - E: The error type (must support the semigroup operation)
|
|
// - A: The input type to the function
|
|
//
|
|
// Parameters:
|
|
// - sg: A semigroup that defines how to combine two error values
|
|
//
|
|
// Returns:
|
|
// - A function that takes a wrapped function and a wrapped value, returning
|
|
// Either[E, B] with accumulated errors or the computed result
|
|
//
|
|
// Example:
|
|
//
|
|
// // Define a semigroup that concatenates error messages
|
|
// errorSemigroup := semigroup.MakeSemigroup(func(e1, e2 string) string {
|
|
// return e1 + "; " + e2
|
|
// })
|
|
//
|
|
// // Create the validation applicative
|
|
// applyV := either.MonadApV[int](errorSemigroup)
|
|
//
|
|
// // Both are errors - errors get combined
|
|
// fab := either.Left[func(int) int]("error1")
|
|
// fa := either.Left[int]("error2")
|
|
// result := applyV(fab, fa) // Left("error1; error2")
|
|
//
|
|
// // One error - returns that error
|
|
// fab2 := either.Right[string](N.Mul(2))
|
|
// fa2 := either.Left[int]("validation failed")
|
|
// result2 := applyV(fab2, fa2) // Left("validation failed")
|
|
//
|
|
// // Both success - applies function
|
|
// fab3 := either.Right[string](N.Mul(2))
|
|
// fa3 := either.Right[string](21)
|
|
// result3 := applyV(fab3, fa3) // Right(42)
|
|
func MonadApV[B, A, E any](sg S.Semigroup[E]) func(fab Either[E, func(a A) B], fa Either[E, A]) Either[E, B] {
|
|
return func(fab Either[E, func(a A) B], fa Either[E, A]) Either[E, B] {
|
|
if fab.isLeft {
|
|
if fa.isLeft {
|
|
return Left[B](sg.Concat(fab.l, fa.l))
|
|
}
|
|
return Left[B](fab.l)
|
|
}
|
|
if fa.isLeft {
|
|
return Left[B](fa.l)
|
|
}
|
|
return Of[E](fab.r(fa.r))
|
|
}
|
|
}
|
|
|
|
// ApV is the curried version of [MonadApV] that combines errors using a semigroup.
|
|
//
|
|
// This function provides a more convenient API for validation scenarios by currying
|
|
// the arguments. It first takes the value to validate, then returns a function that
|
|
// takes the validation function. This allows for a more natural composition style.
|
|
//
|
|
// Like [MonadApV], this accumulates all errors using the provided semigroup instead
|
|
// of short-circuiting on the first error. This is the key difference from the
|
|
// standard [Ap] function.
|
|
//
|
|
// Type Parameters:
|
|
// - B: The result type after applying the function
|
|
// - E: The error type (must support the semigroup operation)
|
|
// - A: The input type to the function
|
|
//
|
|
// Parameters:
|
|
// - sg: A semigroup that defines how to combine two error values
|
|
//
|
|
// Returns:
|
|
// - A function that takes a value Either[E, A] and returns an Operator that
|
|
// applies validation functions while accumulating errors
|
|
//
|
|
// Example:
|
|
//
|
|
// // Define a semigroup for combining validation errors
|
|
// type ValidationError struct {
|
|
// Errors []string
|
|
// }
|
|
// errorSemigroup := semigroup.MakeSemigroup(func(e1, e2 ValidationError) ValidationError {
|
|
// return ValidationError{Errors: append(e1.Errors, e2.Errors...)}
|
|
// })
|
|
//
|
|
// // Create validators
|
|
// validatePositive := func(x int) either.Either[ValidationError, int] {
|
|
// if x > 0 {
|
|
// return either.Right[ValidationError](x)
|
|
// }
|
|
// return either.Left[int](ValidationError{Errors: []string{"must be positive"}})
|
|
// }
|
|
//
|
|
// // Use ApV for validation
|
|
// applyValidation := either.ApV[int](errorSemigroup)
|
|
// value := either.Left[int](ValidationError{Errors: []string{"invalid input"}})
|
|
// validator := either.Left[func(int) int](ValidationError{Errors: []string{"invalid validator"}})
|
|
//
|
|
// result := applyValidation(value)(validator)
|
|
// // Left(ValidationError{Errors: []string{"invalid validator", "invalid input"}})
|
|
//
|
|
//go:inline
|
|
func ApV[B, A, E any](sg S.Semigroup[E]) func(Either[E, A]) Operator[E, func(A) B, B] {
|
|
apv := MonadApV[B, A](sg)
|
|
return func(e Either[E, A]) Operator[E, func(A) B, B] {
|
|
return F.Bind2nd(apv, e)
|
|
}
|
|
}
|