1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-11-23 22:14:53 +02:00

fix: add result

Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
Dr. Carsten Leue
2025-11-17 20:36:06 +01:00
parent 03d9720a29
commit 8a2e9539b1
49 changed files with 6226 additions and 8 deletions

View File

@@ -4,7 +4,9 @@
"Bash(go test:*)", "Bash(go test:*)",
"Bash(go tool cover:*)", "Bash(go tool cover:*)",
"Bash(sort:*)", "Bash(sort:*)",
"Bash(timeout 30 go test:*)" "Bash(timeout 30 go test:*)",
"Bash(cut:*)",
"Bash(go build:*)"
], ],
"deny": [], "deny": [],
"ask": [] "ask": []

View File

@@ -20,6 +20,7 @@ import (
F "github.com/IBM/fp-go/v2/function" F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/utils" "github.com/IBM/fp-go/v2/internal/utils"
N "github.com/IBM/fp-go/v2/number"
L "github.com/IBM/fp-go/v2/optics/lens" L "github.com/IBM/fp-go/v2/optics/lens"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -203,7 +204,7 @@ func TestLetL(t *testing.T) {
) )
t.Run("LetL with pure transformation", func(t *testing.T) { t.Run("LetL with pure transformation", func(t *testing.T) {
double := func(v int) int { return v * 2 } double := N.Mul(2)
result := F.Pipe1( result := F.Pipe1(
Right[error](Counter{Value: 21}), Right[error](Counter{Value: 21}),
@@ -215,7 +216,7 @@ func TestLetL(t *testing.T) {
}) })
t.Run("LetL with Left input", func(t *testing.T) { t.Run("LetL with Left input", func(t *testing.T) {
double := func(v int) int { return v * 2 } double := N.Mul(2)
result := F.Pipe1( result := F.Pipe1(
Left[Counter](assert.AnError), Left[Counter](assert.AnError),
@@ -227,8 +228,8 @@ func TestLetL(t *testing.T) {
}) })
t.Run("LetL with multiple transformations", func(t *testing.T) { t.Run("LetL with multiple transformations", func(t *testing.T) {
double := func(v int) int { return v * 2 } double := N.Mul(2)
addTen := func(v int) int { return v + 10 } addTen := N.Add(10)
result := F.Pipe2( result := F.Pipe2(
Right[error](Counter{Value: 5}), Right[error](Counter{Value: 5}),
@@ -241,7 +242,7 @@ func TestLetL(t *testing.T) {
}) })
t.Run("LetL with identity transformation", func(t *testing.T) { t.Run("LetL with identity transformation", func(t *testing.T) {
identity := func(v int) int { return v } identity := F.Identity[int]
result := F.Pipe1( result := F.Pipe1(
Right[error](Counter{Value: 42}), Right[error](Counter{Value: 42}),
@@ -315,7 +316,7 @@ func TestLensOperationsCombined(t *testing.T) {
) )
t.Run("Combine LetToL and LetL", func(t *testing.T) { t.Run("Combine LetToL and LetL", func(t *testing.T) {
double := func(v int) int { return v * 2 } double := N.Mul(2)
result := F.Pipe2( result := F.Pipe2(
Right[error](Counter{Value: 100}), Right[error](Counter{Value: 100}),
@@ -328,7 +329,7 @@ func TestLensOperationsCombined(t *testing.T) {
}) })
t.Run("Combine LetL and BindL", func(t *testing.T) { t.Run("Combine LetL and BindL", func(t *testing.T) {
double := func(v int) int { return v * 2 } double := N.Mul(2)
validate := func(v int) Either[error, int] { validate := func(v int) Either[error, int] {
if v > 100 { if v > 100 {
return Left[int](assert.AnError) return Left[int](assert.AnError)

View File

@@ -0,0 +1,6 @@
package result
type Applicative[A, B any] interface {
Apply[A, B]
Pointed[A]
}

View File

@@ -0,0 +1,50 @@
// Copyright (c) 2023 - 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 result
import (
M "github.com/IBM/fp-go/v2/monoid"
S "github.com/IBM/fp-go/v2/semigroup"
)
// ApplySemigroup lifts a Semigroup over the Right values of Either.
// Combines two Right values using the provided Semigroup.
// If either value is Left, returns the first Left encountered.
//
// Example:
//
// intAdd := semigroup.MakeSemigroup(func(a, b int) int { return a + b })
// eitherSemi := either.ApplySemigroup[error](intAdd)
// result := eitherSemi.Concat(either.Right[error](2), either.Right[error](3)) // Right(5)
//
//go:inline
func ApplySemigroup[A any](s S.Semigroup[A]) S.Semigroup[Either[A]] {
return S.ApplySemigroup(MonadMap[A, func(A) A], MonadAp[A, E, A], s)
}
// ApplicativeMonoid returns a Monoid that concatenates Either instances via their applicative.
// Provides an empty Either (Right with monoid's empty value) and combines Right values using the monoid.
//
// Example:
//
// intAddMonoid := monoid.MakeMonoid(0, func(a, b int) int { return a + b })
// eitherMon := either.ApplicativeMonoid[error](intAddMonoid)
// empty := eitherMon.Empty() // Right(0)
//
//go:inline
func ApplicativeMonoid[A any](m M.Monoid[A]) M.Monoid[Either[A]] {
return M.ApplicativeMonoid(Of[A], MonadMap[A, func(A) A], MonadAp[A, E, A], m)
}

View File

@@ -0,0 +1,6 @@
package result
type Apply[A, B any] interface {
Functor[A, B]
Ap(A, error) Operator[func(A) B, B]
}

View File

@@ -0,0 +1,63 @@
// Copyright (c) 2023 - 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 result
import (
"testing"
M "github.com/IBM/fp-go/v2/monoid/testing"
N "github.com/IBM/fp-go/v2/number"
"github.com/stretchr/testify/assert"
)
func TestApplySemigroup(t *testing.T) {
sg := ApplySemigroup[string](N.SemigroupSum[int]())
la := Left[int]("a")
lb := Left[int]("b")
r1 := Right[string](1)
r2 := Right[string](2)
r3 := Right[string](3)
assert.Equal(t, la, sg.Concat(la, lb))
assert.Equal(t, lb, sg.Concat(r1, lb))
assert.Equal(t, la, sg.Concat(la, r2))
assert.Equal(t, lb, sg.Concat(r1, lb))
assert.Equal(t, r3, sg.Concat(r1, r2))
}
func TestApplicativeMonoid(t *testing.T) {
m := ApplicativeMonoid[string](N.MonoidSum[int]())
la := Left[int]("a")
lb := Left[int]("b")
r1 := Right[string](1)
r2 := Right[string](2)
r3 := Right[string](3)
assert.Equal(t, la, m.Concat(la, m.Empty()))
assert.Equal(t, lb, m.Concat(m.Empty(), lb))
assert.Equal(t, r1, m.Concat(r1, m.Empty()))
assert.Equal(t, r2, m.Concat(m.Empty(), r2))
assert.Equal(t, r3, m.Concat(r1, r2))
}
func TestApplicativeMonoidLaws(t *testing.T) {
m := ApplicativeMonoid[string](N.MonoidSum[int]())
M.AssertLaws(t, m)([]Either[string, int]{Left[int]("a"), Right[string](1)})
}

View File

@@ -0,0 +1,184 @@
// Copyright (c) 2023 - 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 result
// TraverseArrayG transforms an array by applying a function that returns a Result (value, error) to each element.
// It processes elements from left to right, applying the function to each.
// If any element produces an error, the entire operation short-circuits and returns that error.
// Otherwise, it returns a successful result containing the array of all transformed values.
//
// The G suffix indicates support for generic slice types (e.g., custom slice types based on []T).
//
// Type Parameters:
// - GA: Source slice type (must be based on []A)
// - GB: Target slice type (must be based on []B)
// - A: Source element type
// - B: Target element type
//
// Parameters:
// - f: A Kleisli arrow (A) -> (B, error) that transforms each element
//
// Returns:
// - A Kleisli arrow (GA) -> (GB, error) that transforms the entire array
//
// Behavior:
// - Short-circuits on the first error encountered
// - Preserves the order of elements
// - Returns an empty slice for empty input
//
// Example - Parse strings to integers:
//
// parse := func(s string) (int, error) {
// return strconv.Atoi(s)
// }
// result := result.TraverseArrayG[[]string, []int](parse)([]string{"1", "2", "3"})
// // result is ([]int{1, 2, 3}, nil)
//
// Example - Short-circuit on error:
//
// result := result.TraverseArrayG[[]string, []int](parse)([]string{"1", "bad", "3"})
// // result is ([]int(nil), error) - stops at "bad"
func TraverseArrayG[GA ~[]A, GB ~[]B, A, B any](f Kleisli[A, B]) Kleisli[GA, GB] {
return func(ga GA) (GB, error) {
bs := make(GB, len(ga))
for i, a := range ga {
b, err := f(a)
if err != nil {
return Left[GB](err)
}
bs[i] = b
}
return Of(bs)
}
}
// TraverseArray transforms an array by applying a function that returns a Result (value, error) to each element.
// It processes elements from left to right, applying the function to each.
// If any element produces an error, the entire operation short-circuits and returns that error.
// Otherwise, it returns a successful result containing the array of all transformed values.
//
// This is a convenience wrapper around [TraverseArrayG] for standard slice types.
//
// Type Parameters:
// - A: Source element type
// - B: Target element type
//
// Parameters:
// - f: A Kleisli arrow (A) -> (B, error) that transforms each element
//
// Returns:
// - A Kleisli arrow ([]A) -> ([]B, error) that transforms the entire array
//
// Example - Validate and transform:
//
// validate := func(s string) (int, error) {
// n, err := strconv.Atoi(s)
// if err != nil {
// return 0, err
// }
// if n < 0 {
// return 0, errors.New("negative number")
// }
// return n * 2, nil
// }
// result := result.TraverseArray(validate)([]string{"1", "2", "3"})
// // result is ([]int{2, 4, 6}, nil)
//
//go:inline
func TraverseArray[A, B any](f Kleisli[A, B]) Kleisli[[]A, []B] {
return TraverseArrayG[[]A, []B](f)
}
// TraverseArrayWithIndexG transforms an array by applying an indexed function that returns a Result (value, error).
// The function receives both the zero-based index and the element for each iteration.
// If any element produces an error, the entire operation short-circuits and returns that error.
// Otherwise, it returns a successful result containing the array of all transformed values.
//
// The G suffix indicates support for generic slice types (e.g., custom slice types based on []T).
//
// Type Parameters:
// - GA: Source slice type (must be based on []A)
// - GB: Target slice type (must be based on []B)
// - A: Source element type
// - B: Target element type
//
// Parameters:
// - f: An indexed function (int, A) -> (B, error) that transforms each element
//
// Returns:
// - A Kleisli arrow (GA) -> (GB, error) that transforms the entire array
//
// Behavior:
// - Processes elements from left to right with their indices (0, 1, 2, ...)
// - Short-circuits on the first error encountered
// - Preserves the order of elements
//
// Example - Annotate with index:
//
// annotate := func(i int, s string) (string, error) {
// if len(s) == 0 {
// return "", fmt.Errorf("empty string at index %d", i)
// }
// return fmt.Sprintf("[%d]=%s", i, s), nil
// }
// result := result.TraverseArrayWithIndexG[[]string, []string](annotate)([]string{"a", "b", "c"})
// // result is ([]string{"[0]=a", "[1]=b", "[2]=c"}, nil)
func TraverseArrayWithIndexG[GA ~[]A, GB ~[]B, A, B any](f func(int, A) (B, error)) Kleisli[GA, GB] {
return func(ga GA) (GB, error) {
bs := make(GB, len(ga))
for i, a := range ga {
b, err := f(i, a)
if err != nil {
return Left[GB](err)
}
bs[i] = b
}
return Of(bs)
}
}
// TraverseArrayWithIndex transforms an array by applying an indexed function that returns a Result (value, error).
// The function receives both the zero-based index and the element for each iteration.
// If any element produces an error, the entire operation short-circuits and returns that error.
// Otherwise, it returns a successful result containing the array of all transformed values.
//
// This is a convenience wrapper around [TraverseArrayWithIndexG] for standard slice types.
//
// Type Parameters:
// - A: Source element type
// - B: Target element type
//
// Parameters:
// - f: An indexed function (int, A) -> (B, error) that transforms each element
//
// Returns:
// - A Kleisli arrow ([]A) -> ([]B, error) that transforms the entire array
//
// Example - Validate with position info:
//
// check := func(i int, s string) (string, error) {
// if len(s) == 0 {
// return "", fmt.Errorf("empty value at position %d", i)
// }
// return strings.ToUpper(s), nil
// }
// result := result.TraverseArrayWithIndex(check)([]string{"a", "b", "c"})
// // result is ([]string{"A", "B", "C"}, nil)
//
//go:inline
func TraverseArrayWithIndex[A, B any](f func(int, A) (B, error)) Kleisli[[]A, []B] {
return TraverseArrayWithIndexG[[]A, []B](f)
}

View File

@@ -0,0 +1,419 @@
// Copyright (c) 2023 - 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 result
import (
"errors"
"fmt"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestTraverseArrayG_Success tests successful traversal of an array with all valid elements
func TestTraverseArrayG_Success(t *testing.T) {
parse := func(s string) (int, error) {
return strconv.Atoi(s)
}
result, err := TraverseArrayG[[]string, []int](parse)([]string{"1", "2", "3"})
require.NoError(t, err)
assert.Equal(t, []int{1, 2, 3}, result)
}
// TestTraverseArrayG_Error tests that traversal short-circuits on first error
func TestTraverseArrayG_Error(t *testing.T) {
parse := func(s string) (int, error) {
return strconv.Atoi(s)
}
result, err := TraverseArrayG[[]string, []int](parse)([]string{"1", "bad", "3"})
require.Error(t, err)
assert.Nil(t, result)
}
// TestTraverseArrayG_EmptyArray tests traversal of an empty array
func TestTraverseArrayG_EmptyArray(t *testing.T) {
parse := func(s string) (int, error) {
return strconv.Atoi(s)
}
result, err := TraverseArrayG[[]string, []int](parse)([]string{})
require.NoError(t, err)
assert.Empty(t, result)
assert.NotNil(t, result) // Should be an empty slice, not nil
}
// TestTraverseArrayG_SingleElement tests traversal with a single element
func TestTraverseArrayG_SingleElement(t *testing.T) {
parse := func(s string) (int, error) {
return strconv.Atoi(s)
}
result, err := TraverseArrayG[[]string, []int](parse)([]string{"42"})
require.NoError(t, err)
assert.Equal(t, []int{42}, result)
}
// TestTraverseArrayG_ShortCircuit tests that processing stops at first error
func TestTraverseArrayG_ShortCircuit(t *testing.T) {
callCount := 0
parse := func(s string) (int, error) {
callCount++
if s == "error" {
return 0, errors.New("parse error")
}
return len(s), nil
}
_, err := TraverseArrayG[[]string, []int](parse)([]string{"ok", "error", "should-not-process"})
require.Error(t, err)
assert.Equal(t, 2, callCount, "should stop after encountering error")
}
// TestTraverseArrayG_CustomSliceType tests with custom slice types
func TestTraverseArrayG_CustomSliceType(t *testing.T) {
type StringSlice []string
type IntSlice []int
parse := func(s string) (int, error) {
return strconv.Atoi(s)
}
input := StringSlice{"1", "2", "3"}
result, err := TraverseArrayG[StringSlice, IntSlice](parse)(input)
require.NoError(t, err)
assert.Equal(t, IntSlice{1, 2, 3}, result)
}
// TestTraverseArray_Success tests successful traversal
func TestTraverseArray_Success(t *testing.T) {
validate := func(s string) (int, error) {
n, err := strconv.Atoi(s)
if err != nil {
return 0, err
}
if n < 0 {
return 0, errors.New("negative number")
}
return n * 2, nil
}
result, err := TraverseArray(validate)([]string{"1", "2", "3"})
require.NoError(t, err)
assert.Equal(t, []int{2, 4, 6}, result)
}
// TestTraverseArray_ValidationError tests validation failure
func TestTraverseArray_ValidationError(t *testing.T) {
validate := func(s string) (int, error) {
n, err := strconv.Atoi(s)
if err != nil {
return 0, err
}
if n < 0 {
return 0, errors.New("negative number")
}
return n * 2, nil
}
result, err := TraverseArray(validate)([]string{"1", "-5", "3"})
require.Error(t, err)
assert.Contains(t, err.Error(), "negative number")
assert.Nil(t, result)
}
// TestTraverseArray_ParseError tests parse failure
func TestTraverseArray_ParseError(t *testing.T) {
validate := func(s string) (int, error) {
n, err := strconv.Atoi(s)
if err != nil {
return 0, err
}
return n, nil
}
result, err := TraverseArray(validate)([]string{"1", "not-a-number", "3"})
require.Error(t, err)
assert.Nil(t, result)
}
// TestTraverseArray_EmptyArray tests empty array
func TestTraverseArray_EmptyArray(t *testing.T) {
identity := func(n int) (int, error) {
return n, nil
}
result, err := TraverseArray(identity)([]int{})
require.NoError(t, err)
assert.Empty(t, result)
assert.NotNil(t, result)
}
// TestTraverseArray_DifferentTypes tests transformation between different types
func TestTraverseArray_DifferentTypes(t *testing.T) {
toLength := func(s string) (int, error) {
if len(s) == 0 {
return 0, errors.New("empty string")
}
return len(s), nil
}
result, err := TraverseArray(toLength)([]string{"a", "bb", "ccc"})
require.NoError(t, err)
assert.Equal(t, []int{1, 2, 3}, result)
}
// TestTraverseArrayWithIndexG_Success tests successful indexed traversal
func TestTraverseArrayWithIndexG_Success(t *testing.T) {
annotate := func(i int, s string) (string, error) {
if len(s) == 0 {
return "", fmt.Errorf("empty string at index %d", i)
}
return fmt.Sprintf("[%d]=%s", i, s), nil
}
result, err := TraverseArrayWithIndexG[[]string, []string](annotate)([]string{"a", "b", "c"})
require.NoError(t, err)
assert.Equal(t, []string{"[0]=a", "[1]=b", "[2]=c"}, result)
}
// TestTraverseArrayWithIndexG_Error tests error handling with index
func TestTraverseArrayWithIndexG_Error(t *testing.T) {
annotate := func(i int, s string) (string, error) {
if len(s) == 0 {
return "", fmt.Errorf("empty string at index %d", i)
}
return fmt.Sprintf("[%d]=%s", i, s), nil
}
result, err := TraverseArrayWithIndexG[[]string, []string](annotate)([]string{"a", "", "c"})
require.Error(t, err)
assert.Contains(t, err.Error(), "index 1")
assert.Nil(t, result)
}
// TestTraverseArrayWithIndexG_EmptyArray tests empty array
func TestTraverseArrayWithIndexG_EmptyArray(t *testing.T) {
annotate := func(i int, s string) (string, error) {
return fmt.Sprintf("%d:%s", i, s), nil
}
result, err := TraverseArrayWithIndexG[[]string, []string](annotate)([]string{})
require.NoError(t, err)
assert.Empty(t, result)
assert.NotNil(t, result)
}
// TestTraverseArrayWithIndexG_IndexValidation tests that indices are correct
func TestTraverseArrayWithIndexG_IndexValidation(t *testing.T) {
indices := []int{}
collect := func(i int, s string) (string, error) {
indices = append(indices, i)
return s, nil
}
_, err := TraverseArrayWithIndexG[[]string, []string](collect)([]string{"a", "b", "c", "d"})
require.NoError(t, err)
assert.Equal(t, []int{0, 1, 2, 3}, indices)
}
// TestTraverseArrayWithIndexG_ShortCircuit tests short-circuit behavior
func TestTraverseArrayWithIndexG_ShortCircuit(t *testing.T) {
maxIndex := -1
process := func(i int, s string) (string, error) {
maxIndex = i
if i == 2 {
return "", errors.New("stop at index 2")
}
return s, nil
}
_, err := TraverseArrayWithIndexG[[]string, []string](process)([]string{"a", "b", "c", "d", "e"})
require.Error(t, err)
assert.Equal(t, 2, maxIndex, "should stop at index 2")
}
// TestTraverseArrayWithIndexG_CustomSliceType tests with custom slice types
func TestTraverseArrayWithIndexG_CustomSliceType(t *testing.T) {
type StringSlice []string
type ResultSlice []string
annotate := func(i int, s string) (string, error) {
return fmt.Sprintf("%d:%s", i, s), nil
}
input := StringSlice{"x", "y", "z"}
result, err := TraverseArrayWithIndexG[StringSlice, ResultSlice](annotate)(input)
require.NoError(t, err)
assert.Equal(t, ResultSlice{"0:x", "1:y", "2:z"}, result)
}
// TestTraverseArrayWithIndex_Success tests successful indexed traversal
func TestTraverseArrayWithIndex_Success(t *testing.T) {
check := func(i int, s string) (string, error) {
if len(s) == 0 {
return "", fmt.Errorf("empty value at position %d", i)
}
return fmt.Sprintf("%d_%s", i, s), nil
}
result, err := TraverseArrayWithIndex(check)([]string{"a", "b", "c"})
require.NoError(t, err)
assert.Equal(t, []string{"0_a", "1_b", "2_c"}, result)
}
// TestTraverseArrayWithIndex_Error tests error with position info
func TestTraverseArrayWithIndex_Error(t *testing.T) {
check := func(i int, s string) (string, error) {
if len(s) == 0 {
return "", fmt.Errorf("empty value at position %d", i)
}
return s, nil
}
result, err := TraverseArrayWithIndex(check)([]string{"ok", "ok", "", "ok"})
require.Error(t, err)
assert.Contains(t, err.Error(), "position 2")
assert.Nil(t, result)
}
// TestTraverseArrayWithIndex_TypeTransformation tests transforming types with index
func TestTraverseArrayWithIndex_TypeTransformation(t *testing.T) {
multiply := func(i int, n int) (int, error) {
return n * (i + 1), nil
}
result, err := TraverseArrayWithIndex(multiply)([]int{10, 20, 30})
require.NoError(t, err)
assert.Equal(t, []int{10, 40, 90}, result) // [10*(0+1), 20*(1+1), 30*(2+1)]
}
// TestTraverseArrayWithIndex_EmptyArray tests empty array
func TestTraverseArrayWithIndex_EmptyArray(t *testing.T) {
process := func(i int, s string) (int, error) {
return i, nil
}
result, err := TraverseArrayWithIndex(process)([]string{})
require.NoError(t, err)
assert.Empty(t, result)
assert.NotNil(t, result)
}
// TestTraverseArrayWithIndex_SingleElement tests single element processing
func TestTraverseArrayWithIndex_SingleElement(t *testing.T) {
annotate := func(i int, s string) (string, error) {
return fmt.Sprintf("item_%d:%s", i, s), nil
}
result, err := TraverseArrayWithIndex(annotate)([]string{"solo"})
require.NoError(t, err)
assert.Equal(t, []string{"item_0:solo"}, result)
}
// TestTraverseArrayWithIndex_ConditionalLogic tests using index for conditional logic
func TestTraverseArrayWithIndex_ConditionalLogic(t *testing.T) {
// Only accept even indices
process := func(i int, s string) (string, error) {
if i%2 != 0 {
return "", fmt.Errorf("odd index %d not allowed", i)
}
return s, nil
}
result, err := TraverseArrayWithIndex(process)([]string{"ok", "fail"})
require.Error(t, err)
assert.Contains(t, err.Error(), "odd index 1")
assert.Nil(t, result)
}
// TestTraverseArray_LargeArray tests traversal with a larger array
func TestTraverseArray_LargeArray(t *testing.T) {
// Create array with 1000 elements
input := make([]int, 1000)
for i := range input {
input[i] = i
}
double := func(n int) (int, error) {
return n * 2, nil
}
result, err := TraverseArray(double)(input)
require.NoError(t, err)
assert.Len(t, result, 1000)
assert.Equal(t, 0, result[0])
assert.Equal(t, 1998, result[999])
}
// TestTraverseArrayG_PreservesOrder tests that order is preserved
func TestTraverseArrayG_PreservesOrder(t *testing.T) {
reverse := func(s string) (string, error) {
runes := []rune(s)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes), nil
}
result, err := TraverseArrayG[[]string, []string](reverse)([]string{"abc", "def", "ghi"})
require.NoError(t, err)
assert.Equal(t, []string{"cba", "fed", "ihg"}, result)
}
// TestTraverseArrayWithIndex_BoundaryCheck tests boundary conditions with index
func TestTraverseArrayWithIndex_BoundaryCheck(t *testing.T) {
// Reject if index exceeds a threshold
limitedProcess := func(i int, s string) (string, error) {
if i >= 100 {
return "", errors.New("index too large")
}
return s, nil
}
// Should succeed with index < 100
result, err := TraverseArrayWithIndex(limitedProcess)([]string{"a", "b", "c"})
require.NoError(t, err)
assert.Equal(t, []string{"a", "b", "c"}, result)
}

View File

@@ -0,0 +1,18 @@
package result
import (
"testing"
"github.com/stretchr/testify/assert"
)
func AssertEq[A any](l A, lerr error) func(A, error) func(*testing.T) {
return func(r A, rerr error) func(*testing.T) {
return func(t *testing.T) {
assert.Equal(t, lerr, rerr)
if (lerr != nil) && (rerr != nil) {
assert.Equal(t, l, r)
}
}
}
}

384
v2/idiomatic/result/bind.go Normal file
View File

@@ -0,0 +1,384 @@
// Copyright (c) 2023 - 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 result
import (
L "github.com/IBM/fp-go/v2/optics/lens"
)
// Do creates an empty context of type S to be used with the Bind operation.
// This is the starting point for do-notation style computations.
//
// Example:
//
// type State struct { x, y int }
// result := either.Do[error](State{})
//
//go:inline
func Do[S any](
empty S,
) (S, error) {
return Of(empty)
}
// Bind attaches the result of a computation to a context S1 to produce a context S2.
// This enables building up complex computations in a pipeline.
//
// Example:
//
// type State struct { value int }
// result := F.Pipe2(
// either.Do[error](State{}),
// either.Bind(
// func(v int) func(State) State {
// return func(s State) State { return State{value: v} }
// },
// func(s State) either.Either[error, int] {
// return either.Right[error](42)
// },
// ),
// )
func Bind[S1, S2, T any](
setter func(T) func(S1) S2,
f Kleisli[S1, T],
) Operator[S1, S2] {
return func(s S1, err error) (S2, error) {
if err != nil {
return Left[S2](err)
}
t, err := f(s)
if err != nil {
return Left[S2](err)
}
return Of(setter(t)(s))
}
}
// Let attaches the result of a pure computation to a context S1 to produce a context S2.
// Similar to Bind but for pure (non-Either) computations.
//
// Example:
//
// type State struct { value int }
// result := F.Pipe2(
// either.Right[error](State{value: 10}),
// either.Let(
// func(v int) func(State) State {
// return func(s State) State { return State{value: s.value + v} }
// },
// func(s State) int { return 32 },
// ),
// ) // Right(State{value: 42})
func Let[S1, S2, T any](
key func(T) func(S1) S2,
f func(S1) T,
) Operator[S1, S2] {
return func(s S1, err error) (S2, error) {
if err != nil {
return Left[S2](err)
}
return Of(key(f(s))(s))
}
}
// LetTo attaches a constant value to a context S1 to produce a context S2.
//
// Example:
//
// type State struct { name string }
// result := F.Pipe2(
// either.Right[error](State{}),
// either.LetTo(
// func(n string) func(State) State {
// return func(s State) State { return State{name: n} }
// },
// "Alice",
// ),
// ) // Right(State{name: "Alice"})
func LetTo[S1, S2, T any](
key func(T) func(S1) S2,
t T,
) Operator[S1, S2] {
return func(s S1, err error) (S2, error) {
if err != nil {
return Left[S2](err)
}
return Of(key(t)(s))
}
}
// BindTo initializes a new state S1 from a value T.
// This is typically used to start a bind chain.
//
// Example:
//
// type State struct { value int }
// result := F.Pipe2(
// either.Right[error](42),
// either.BindTo(func(v int) State { return State{value: v} }),
// ) // Right(State{value: 42})
func BindTo[S1, T any](
setter func(T) S1,
) Operator[T, S1] {
return func(t T, err error) (S1, error) {
if err != nil {
return Left[S1](err)
}
return Of(setter(t))
}
}
// ApS attaches a value to a context S1 to produce a context S2 by considering the context and the value concurrently.
// Uses applicative semantics rather than monadic sequencing.
//
// Example:
//
// type State struct { x, y int }
// result := F.Pipe2(
// either.Right[error](State{x: 10}),
// either.ApS(
// func(y int) func(State) State {
// return func(s State) State { return State{x: s.x, y: y} }
// },
// either.Right[error](32),
// ),
// ) // Right(State{x: 10, y: 32})
func ApS[S1, S2, T any](
setter func(T) func(S1) S2,
) func(T, error) Operator[S1, S2] {
return func(t T, terr error) Operator[S1, S2] {
return func(s S1, serr error) (S2, error) {
if terr != nil {
return Left[S2](terr)
}
if serr != nil {
return Left[S2](serr)
}
return Of(setter(t)(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.
//
// 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 and enables working with
// nested fields in a type-safe manner.
//
// Unlike BindL, ApSL uses applicative semantics, meaning the computation fa is independent
// of the current state and can be evaluated concurrently.
//
// Type Parameters:
// - E: Error type for the Either
// - S: Structure type containing the field to update
// - T: Type of the field being updated
//
// Parameters:
// - lens: A Lens[S, T] that focuses on a field of type T within structure S
// - fa: An Either[T] computation that produces the value to set
//
// Returns:
// - An endomorphism that updates the focused field in the Either context
//
// Example:
//
// type Person struct {
// Name string
// Age int
// }
//
// ageLens := lens.MakeLens(
// func(p Person) int { return p.Age },
// func(p Person, a int) Person { p.Age = a; return p },
// )
//
// result := F.Pipe2(
// either.Right[error](Person{Name: "Alice", Age: 25}),
// either.ApSL(ageLens, either.Right[error](30)),
// ) // Right(Person{Name: "Alice", Age: 30})
//
//go:inline
func ApSL[S, T any](
lens Lens[S, T],
) func(T, error) Operator[S, S] {
return ApS(lens.Set)
}
// 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
// an Either 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.
//
// Type Parameters:
// - E: Error type for the Either
// - S: Structure type containing the field to update
// - T: Type of the field being updated
//
// Parameters:
// - lens: A Lens[S, T] that focuses on a field of type T within structure S
// - f: A function that takes the current field value and returns an Either[T]
//
// Returns:
// - An endomorphism that updates the focused field based on its current value
//
// 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) either.Either[error, int] {
// if v >= 100 {
// return either.Left[int](errors.New("counter overflow"))
// }
// return either.Right[error](v + 1)
// }
//
// result := F.Pipe1(
// either.Right[error](Counter{Value: 42}),
// either.BindL(valueLens, increment),
// ) // Right(Counter{Value: 43})
func BindL[S, T any](
lens Lens[S, T],
f Kleisli[T, T],
) Operator[S, S] {
return func(s S, serr error) (S, error) {
t, terr := f(lens.Get(s))
if terr != nil {
return Left[S](terr)
}
if serr != nil {
return Left[S](serr)
}
return Of(lens.Set(t)(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 Either).
//
// This is useful for pure transformations that cannot fail, such as mathematical operations,
// string manipulations, or other deterministic updates.
//
// Type Parameters:
// - E: Error type for the Either
// - S: Structure type containing the field to update
// - T: Type of the field being updated
//
// Parameters:
// - lens: A Lens[S, T] that focuses on a field of type T within structure S
// - f: An endomorphism (T → T) that transforms the current field value
//
// Returns:
// - An endomorphism that updates the focused field with the transformed value
//
// 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 }
//
// result := F.Pipe1(
// either.Right[error](Counter{Value: 21}),
// either.LetL(valueLens, double),
// ) // Right(Counter{Value: 42})
func LetL[S, T any](
lens Lens[S, T],
f Endomorphism[T],
) Operator[S, S] {
mod := L.Modify[S](f)(lens)
return func(s S, err error) (S, error) {
if err != nil {
return Left[S](err)
}
return Of(mod(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.
//
// Type Parameters:
// - E: Error type for the Either
// - S: Structure type containing the field to update
// - T: Type of the field being updated
//
// Parameters:
// - lens: A Lens[S, T] that focuses on a field of type T within structure S
// - b: The constant value to set the field to
//
// Returns:
// - An endomorphism that sets the focused field to the constant value
//
// 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 },
// )
//
// result := F.Pipe1(
// either.Right[error](Config{Debug: true, Timeout: 30}),
// either.LetToL(debugLens, false),
// ) // Right(Config{Debug: false, Timeout: 30})
//
//go:inline
func LetToL[S, T any](
lens Lens[S, T],
b T,
) Operator[S, S] {
return LetTo(lens.Set, b)
}

View File

@@ -0,0 +1,363 @@
// Copyright (c) 2023 - 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 result
import (
"testing"
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/internal/utils"
N "github.com/IBM/fp-go/v2/number"
L "github.com/IBM/fp-go/v2/optics/lens"
"github.com/stretchr/testify/assert"
)
func getLastName(s utils.Initial) (string, error) {
return Of("Doe")
}
func getGivenName(s utils.WithLastName) (string, error) {
return Of("John")
}
func TestBind(t *testing.T) {
res, err := Pipe4(
utils.Empty,
Do,
Bind(utils.SetLastName, getLastName),
Bind(utils.SetGivenName, getGivenName),
Map(utils.GetFullName),
)
AssertEq(Of("John Doe"))(res, err)(t)
}
func TestApS(t *testing.T) {
res, err := Pipe4(
utils.Empty,
Do,
ApS(utils.SetLastName)(Of("Doe")),
ApS(utils.SetGivenName)(Of("John")),
Map(utils.GetFullName),
)
AssertEq(Of("John Doe"))(res, err)(t)
}
// Test types for lens-based operations
type Counter struct {
Value int
}
type Person struct {
Name string
Age int
}
type Config struct {
Debug bool
Timeout int
}
func TestApSL(t *testing.T) {
// Create a lens for the Age field
ageLens := L.MakeLens(
func(p Person) int { return p.Age },
func(p Person, a int) Person { p.Age = a; return p },
)
t.Run("ApSL with Right value", func(t *testing.T) {
result, err := Pipe2(
Person{Name: "Alice", Age: 25},
Right,
ApSL(ageLens)(Right(30)),
)
AssertEq(Right(Person{Name: "Alice", Age: 30}))(result, err)(t)
})
t.Run("ApSL with Left in context", func(t *testing.T) {
result, err := Pipe2(
assert.AnError,
Left[Person],
ApSL(ageLens)(Right(30)),
)
AssertEq(Left[Person](assert.AnError))(result, err)(t)
})
t.Run("ApSL with Left in value", func(t *testing.T) {
result, err := Pipe2(
Person{Name: "Alice", Age: 25},
Right,
ApSL(ageLens)(Left[int](assert.AnError)),
)
AssertEq(Left[Person](assert.AnError))(result, err)(t)
})
t.Run("ApSL with both Left", func(t *testing.T) {
result, err := Pipe2(
assert.AnError,
Left[Person],
ApSL(ageLens)(Left[int](assert.AnError)),
)
AssertEq(Left[Person](assert.AnError))(result, err)(t)
})
}
func TestBindL(t *testing.T) {
// Create a lens for the Value field
valueLens := L.MakeLens(
func(c Counter) int { return c.Value },
func(c Counter, v int) Counter { c.Value = v; return c },
)
t.Run("BindL with successful transformation", func(t *testing.T) {
// Increment the counter, but fail if it would exceed 100
increment := func(v int) (int, error) {
if v >= 100 {
return Left[int](assert.AnError)
}
return Right(v + 1)
}
result, err := Pipe2(
Counter{Value: 42},
Right,
BindL(valueLens, increment),
)
AssertEq(Right(Counter{Value: 43}))(result, err)(t)
})
t.Run("BindL with failing transformation", func(t *testing.T) {
increment := func(v int) (int, error) {
if v >= 100 {
return Left[int](assert.AnError)
}
return Right(v + 1)
}
result, err := Pipe2(
Counter{Value: 100},
Right,
BindL(valueLens, increment),
)
AssertEq(Left[Counter](assert.AnError))(result, err)(t)
})
t.Run("BindL with Left input", func(t *testing.T) {
increment := func(v int) (int, error) {
return Right(v + 1)
}
result, err := Pipe2(
assert.AnError,
Left[Counter],
BindL(valueLens, increment),
)
AssertEq(Left[Counter](assert.AnError))(result, err)(t)
})
t.Run("BindL with multiple operations", func(t *testing.T) {
double := func(v int) (int, error) {
return Right(v * 2)
}
addTen := func(v int) (int, error) {
return Right(v + 10)
}
result, err := Pipe3(
Counter{Value: 5},
Right,
BindL(valueLens, double),
BindL(valueLens, addTen),
)
AssertEq(Right(Counter{Value: 20}))(result, err)(t)
})
}
func TestLetL(t *testing.T) {
// Create a lens for the Value field
valueLens := L.MakeLens(
func(c Counter) int { return c.Value },
func(c Counter, v int) Counter { c.Value = v; return c },
)
t.Run("LetL with pure transformation", func(t *testing.T) {
double := N.Mul(2)
result, err := Pipe2(
Counter{Value: 21},
Right,
LetL(valueLens, double),
)
AssertEq(Right(Counter{Value: 42}))(result, err)(t)
})
t.Run("LetL with Left input", func(t *testing.T) {
double := N.Mul(2)
result, err := Pipe2(
assert.AnError,
Left[Counter],
LetL(valueLens, double),
)
AssertEq(Left[Counter](assert.AnError))(result, err)(t)
})
t.Run("LetL with multiple transformations", func(t *testing.T) {
double := N.Mul(2)
addTen := N.Add(10)
result, err := Pipe3(
Counter{Value: 5},
Right,
LetL(valueLens, double),
LetL(valueLens, addTen),
)
AssertEq(Right(Counter{Value: 20}))(result, err)(t)
})
t.Run("LetL with identity transformation", func(t *testing.T) {
identity := F.Identity[int]
result, err := Pipe2(
Counter{Value: 42},
Right,
LetL(valueLens, identity),
)
AssertEq(Right(Counter{Value: 42}))(result, err)(t)
})
}
func TestLetToL(t *testing.T) {
// Create a lens for the Debug field
debugLens := L.MakeLens(
func(c Config) bool { return c.Debug },
func(c Config, d bool) Config { c.Debug = d; return c },
)
t.Run("LetToL with constant value", func(t *testing.T) {
result, err := Pipe2(
Config{Debug: true, Timeout: 30},
Right,
LetToL(debugLens, false),
)
AssertEq(Right(Config{Debug: false, Timeout: 30}))(result, err)(t)
})
t.Run("LetToL with Left input", func(t *testing.T) {
result, err := Pipe2(
assert.AnError,
Left[Config],
LetToL(debugLens, false),
)
AssertEq(Left[Config](assert.AnError))(result, err)(t)
})
t.Run("LetToL with multiple fields", func(t *testing.T) {
timeoutLens := L.MakeLens(
func(c Config) int { return c.Timeout },
func(c Config, t int) Config { c.Timeout = t; return c },
)
result, err := Pipe3(
Config{Debug: true, Timeout: 30},
Right,
LetToL(debugLens, false),
LetToL(timeoutLens, 60),
)
AssertEq(Right(Config{Debug: false, Timeout: 60}))(result, err)(t)
})
t.Run("LetToL setting same value", func(t *testing.T) {
result, err := Pipe2(
Config{Debug: false, Timeout: 30},
Right,
LetToL(debugLens, false),
)
AssertEq(Right(Config{Debug: false, Timeout: 30}))(result, err)(t)
})
}
func TestLensOperationsCombined(t *testing.T) {
// Test combining different lens operations
valueLens := L.MakeLens(
func(c Counter) int { return c.Value },
func(c Counter, v int) Counter { c.Value = v; return c },
)
t.Run("Combine LetToL and LetL", func(t *testing.T) {
double := N.Mul(2)
result, err := Pipe3(
Counter{Value: 100},
Right,
LetToL(valueLens, 10),
LetL(valueLens, double),
)
AssertEq(Right(Counter{Value: 20}))(result, err)(t)
})
t.Run("Combine LetL and BindL", func(t *testing.T) {
double := N.Mul(2)
validate := func(v int) (int, error) {
if v > 100 {
return Left[int](assert.AnError)
}
return Right(v)
}
result, err := Pipe3(
Counter{Value: 25},
Right,
LetL(valueLens, double),
BindL(valueLens, validate),
)
AssertEq(Right(Counter{Value: 50}))(result, err)(t)
})
t.Run("Combine ApSL and LetL", func(t *testing.T) {
addFive := func(v int) int { return v + 5 }
result, err := Pipe3(
Counter{Value: 10},
Right,
ApSL(valueLens)(Right(20)),
LetL(valueLens, addFive),
)
AssertEq(Right(Counter{Value: 25}))(result, err)(t)
})
}

View File

@@ -0,0 +1,6 @@
package result
type Chainable[A, B any] interface {
Apply[A, B]
Chain(Kleisli[A, B]) Operator[A, B]
}

View File

@@ -0,0 +1,80 @@
// Copyright (c) 2023 - 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 result
import (
"fmt"
)
// String prints some debug info for the object
func ToString[A any](a A, err error) string {
if err != nil {
return fmt.Sprintf("Left(%v)", err)
}
return fmt.Sprintf("Right[%T](%v)", a, a)
}
// IsLeft tests if the Either is a Left value.
// Rather use [Fold] or [MonadFold] if you need to access the values.
// Inverse is [IsRight].
//
// Example:
//
// either.IsLeft(either.Left[int](errors.New("err"))) // true
// either.IsLeft(either.Right[error](42)) // false
//
//go:inline
func IsLeft[A any](_ A, err error) bool {
return err != nil
}
// IsRight tests if the Either is a Right value.
// Rather use [Fold] or [MonadFold] if you need to access the values.
// Inverse is [IsLeft].
//
// Example:
//
// either.IsRight(either.Right[error](42)) // true
// either.IsRight(either.Left[int](errors.New("err"))) // false
//
//go:inline
func IsRight[A any](_ A, err error) bool {
return err == nil
}
// Left creates a new Either representing a Left (error/failure) value.
// By convention, Left represents the error case.
//
// Example:
//
// result := either.Left[int](errors.New("something went wrong"))
//
//go:inline
func Left[A any](err error) (A, error) {
return *new(A), err
}
// Right creates a new Either representing a Right (success) value.
// By convention, Right represents the success case.
//
// Example:
//
// result := either.Right[error](42)
//
//go:inline
func Right[A any](a A) (A, error) {
return a, nil
}

View File

@@ -0,0 +1,154 @@
// Copyright (c) 2023 - 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.
//go:build either_any
package result
import (
"fmt"
)
type (
either struct {
value any
isRight bool
}
// Either defines a data structure that logically holds either an E or an A. The flag discriminates the cases
Either[A any] either
)
// String prints some debug info for the object
//
//go:noinline
func eitherString(s *either) string {
if s.isRight {
return fmt.Sprintf("Right[%T](%v)", s.value, s.value)
}
return fmt.Sprintf("Left[%T](%v)", s.value, s.value)
}
// Format prints some debug info for the object
//
//go:noinline
func eitherFormat(e *either, f fmt.State, c rune) {
switch c {
case 's':
fmt.Fprint(f, eitherString(e))
default:
fmt.Fprint(f, eitherString(e))
}
}
// String prints some debug info for the object
func (s Either[A]) String() string {
return eitherString((*either)(&s))
}
// Format prints some debug info for the object
func (s Either[A]) Format(f fmt.State, c rune) {
eitherFormat((*either)(&s), f, c)
}
// IsLeft tests if the Either is a Left value.
// Rather use [Fold] or [MonadFold] if you need to access the values.
// Inverse is [IsRight].
//
// Example:
//
// either.IsLeft(either.Left[int](errors.New("err"))) // true
// either.IsLeft(either.Right[error](42)) // false
//
//go:inline
func IsLeft[A any](val Either[A]) bool {
return !val.isRight
}
// IsRight tests if the Either is a Right value.
// Rather use [Fold] or [MonadFold] if you need to access the values.
// Inverse is [IsLeft].
//
// Example:
//
// either.IsRight(either.Right[error](42)) // true
// either.IsRight(either.Left[int](errors.New("err"))) // false
//
//go:inline
func IsRight[A any](val Either[A]) bool {
return val.isRight
}
// Left creates a new Either representing a Left (error/failure) value.
// By convention, Left represents the error case.
//
// Example:
//
// result := either.Left[int](errors.New("something went wrong"))
//
//go:inline
func Left[A, E any](value E) Either[A] {
return Either[A]{value, false}
}
// Right creates a new Either representing a Right (success) value.
// By convention, Right represents the success case.
//
// Example:
//
// result := either.Right[error](42)
//
//go:inline
func Right[A any](value A) Either[A] {
return Either[A]{value, true}
}
// MonadFold extracts the value from an Either by providing handlers for both cases.
// This is the fundamental pattern matching operation for Either.
//
// Example:
//
// result := either.MonadFold(
// either.Right[error](42),
// func(err error) string { return "Error: " + err.Error() },
// func(n int) string { return fmt.Sprintf("Value: %d", n) },
// ) // "Value: 42"
//
//go:inline
func MonadFold[A, B any](ma Either[A], onLeft func(e E) B, onRight func(a A) B) B {
if ma.isRight {
return onRight(ma.value.(A))
}
return onLeft(ma.value.(E))
}
// Unwrap converts an Either into the idiomatic Go tuple (value, error).
// For Right values, returns (value, zero-error).
// For Left values, returns (zero-value, error).
//
// Example:
//
// val, err := either.Unwrap(either.Right[error](42)) // 42, nil
// val, err := either.Unwrap(either.Left[int](errors.New("fail"))) // 0, error
//
//go:inline
func Unwrap[A any](ma Either[A]) (A, E) {
if ma.isRight {
var e E
return ma.value.(A), e
} else {
var a A
return a, ma.value.(E)
}
}

View File

@@ -0,0 +1,94 @@
// Copyright (c) 2023 - 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.
//go:build either_pointers
package result
import "fmt"
type Either[A any] struct {
left *E
right *A
}
// String prints some debug info for the object
//
//go:noinline
func eitherString[A any](s *Either[A]) string {
if s.right != nil {
return fmt.Sprintf("Right[%T](%v)", *s.right, *s.right)
}
return fmt.Sprintf("Left[%T](%v)", *s.left, *s.left)
}
// Format prints some debug info for the object
//
//go:noinline
func eitherFormat[A any](e *Either[A], f fmt.State, c rune) {
switch c {
case 's':
fmt.Fprint(f, eitherString(e))
default:
fmt.Fprint(f, eitherString(e))
}
}
// String prints some debug info for the object
func (s Either[A]) String() string {
return eitherString(&s)
}
// Format prints some debug info for the object
func (s Either[A]) Format(f fmt.State, c rune) {
eitherFormat(&s, f, c)
}
//go:inline
func Left[A, E any](value E) Either[A] {
return Either[A]{left: &value}
}
//go:inline
func Right[A any](value A) Either[A] {
return Either[A]{right: &value}
}
//go:inline
func IsLeft[A any](e Either[A]) bool {
return e.left != nil
}
//go:inline
func IsRight[A any](e Either[A]) bool {
return e.right != nil
}
//go:inline
func MonadFold[A, B any](ma Either[A], onLeft func(E) B, onRight func(A) B) B {
if ma.right != nil {
return onRight(*ma.right)
}
return onLeft(*ma.left)
}
//go:inline
func Unwrap[A any](ma Either[A]) (A, E) {
if ma.right != nil {
var e E
return *ma.right, e
}
var a A
return a, *ma.left
}

View File

@@ -0,0 +1,142 @@
mode: set
github.com/IBM/fp-go/v2/idiomatic/result/array.go:54.82,55.33 1 1
github.com/IBM/fp-go/v2/idiomatic/result/array.go:55.33,57.24 2 1
github.com/IBM/fp-go/v2/idiomatic/result/array.go:57.24,59.18 2 1
github.com/IBM/fp-go/v2/idiomatic/result/array.go:59.18,61.5 1 1
github.com/IBM/fp-go/v2/idiomatic/result/array.go:62.4,62.13 1 1
github.com/IBM/fp-go/v2/idiomatic/result/array.go:64.3,64.16 1 1
github.com/IBM/fp-go/v2/idiomatic/result/array.go:101.65,103.2 1 1
github.com/IBM/fp-go/v2/idiomatic/result/array.go:139.101,140.33 1 1
github.com/IBM/fp-go/v2/idiomatic/result/array.go:140.33,142.24 2 1
github.com/IBM/fp-go/v2/idiomatic/result/array.go:142.24,144.18 2 1
github.com/IBM/fp-go/v2/idiomatic/result/array.go:144.18,146.5 1 1
github.com/IBM/fp-go/v2/idiomatic/result/array.go:147.4,147.13 1 1
github.com/IBM/fp-go/v2/idiomatic/result/array.go:149.3,149.16 1 1
github.com/IBM/fp-go/v2/idiomatic/result/array.go:182.84,184.2 1 1
github.com/IBM/fp-go/v2/idiomatic/result/core.go:23.45,24.16 1 0
github.com/IBM/fp-go/v2/idiomatic/result/core.go:24.16,26.3 1 0
github.com/IBM/fp-go/v2/idiomatic/result/core.go:27.2,27.43 1 0
github.com/IBM/fp-go/v2/idiomatic/result/core.go:40.41,42.2 1 0
github.com/IBM/fp-go/v2/idiomatic/result/core.go:54.42,56.2 1 0
github.com/IBM/fp-go/v2/idiomatic/result/core.go:66.40,68.2 1 1
github.com/IBM/fp-go/v2/idiomatic/result/core.go:78.35,80.2 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:36.32,38.2 1 1
github.com/IBM/fp-go/v2/idiomatic/result/either.go:42.61,43.54 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:43.54,44.20 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:44.20,46.4 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:47.3,47.19 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:47.19,49.4 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:50.3,50.21 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:56.75,57.41 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:57.41,58.17 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:58.17,60.4 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:61.3,61.18 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:66.42,67.41 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:67.41,69.3 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:74.48,75.41 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:75.41,76.17 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:76.17,78.4 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:79.3,79.18 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:85.59,86.41 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:86.41,87.17 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:87.17,89.4 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:90.3,90.15 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:95.92,96.50 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:96.50,97.42 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:97.42,98.18 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:98.18,100.5 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:101.4,101.25 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:101.25,103.5 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:104.4,104.28 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:110.56,111.17 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:111.17,112.40 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:112.40,114.4 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:116.2,116.42 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:116.42,117.18 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:117.18,119.4 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:120.3,120.15 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:126.54,127.42 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:127.42,128.18 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:128.18,130.4 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:131.3,131.14 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:136.59,137.42 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:137.42,138.18 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:138.18,140.4 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:141.3,142.17 2 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:152.70,153.40 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:153.40,154.11 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:154.11,156.4 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:157.3,157.15 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:169.49,171.2 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:183.56,184.30 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:184.30,186.3 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:195.43,197.2 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:208.79,209.33 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:209.33,210.18 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:210.18,212.4 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:213.3,213.20 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:228.83,229.30 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:229.30,230.14 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:230.14,232.4 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:233.3,233.29 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:245.51,246.32 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:246.32,247.17 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:247.17,249.4 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:250.3,250.15 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:260.62,261.32 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:261.32,262.17 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:262.17,264.4 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:265.3,265.11 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:271.67,272.32 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:272.32,273.17 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:273.17,275.4 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:276.3,276.23 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:288.56,289.41 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:289.41,290.17 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:290.17,292.4 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:293.3,293.15 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:305.61,306.41 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:306.41,307.17 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:307.17,309.4 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:310.3,310.15 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:323.61,324.32 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:324.32,325.25 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:325.25,327.4 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:328.3,328.29 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:333.48,335.2 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:338.49,339.54 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:339.54,340.20 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:340.20,342.4 1 0
github.com/IBM/fp-go/v2/idiomatic/result/either.go:343.3,343.20 1 0
github.com/IBM/fp-go/v2/idiomatic/result/function.go:7.76,9.2 1 0
github.com/IBM/fp-go/v2/idiomatic/result/function.go:15.92,17.2 1 0
github.com/IBM/fp-go/v2/idiomatic/result/function.go:23.120,25.2 1 0
github.com/IBM/fp-go/v2/idiomatic/result/function.go:31.136,32.45 1 0
github.com/IBM/fp-go/v2/idiomatic/result/function.go:32.45,34.3 1 0
github.com/IBM/fp-go/v2/idiomatic/result/function.go:41.164,43.2 1 0
github.com/IBM/fp-go/v2/idiomatic/result/function.go:49.180,50.45 1 0
github.com/IBM/fp-go/v2/idiomatic/result/function.go:50.45,52.3 1 0
github.com/IBM/fp-go/v2/idiomatic/result/function.go:59.208,61.2 1 0
github.com/IBM/fp-go/v2/idiomatic/result/function.go:67.224,68.45 1 0
github.com/IBM/fp-go/v2/idiomatic/result/function.go:68.45,70.3 1 0
github.com/IBM/fp-go/v2/idiomatic/result/function.go:77.252,79.2 1 0
github.com/IBM/fp-go/v2/idiomatic/result/function.go:85.268,86.45 1 0
github.com/IBM/fp-go/v2/idiomatic/result/function.go:86.45,88.3 1 0
github.com/IBM/fp-go/v2/idiomatic/result/traverse.go:35.43,36.51 1 0
github.com/IBM/fp-go/v2/idiomatic/result/traverse.go:36.51,38.37 2 0
github.com/IBM/fp-go/v2/idiomatic/result/traverse.go:38.37,39.18 1 0
github.com/IBM/fp-go/v2/idiomatic/result/traverse.go:39.18,41.5 1 0
github.com/IBM/fp-go/v2/idiomatic/result/traverse.go:42.4,42.22 1 0
github.com/IBM/fp-go/v2/idiomatic/result/traverse.go:64.36,66.42 2 0
github.com/IBM/fp-go/v2/idiomatic/result/traverse.go:66.42,67.17 1 0
github.com/IBM/fp-go/v2/idiomatic/result/traverse.go:67.17,69.4 1 0
github.com/IBM/fp-go/v2/idiomatic/result/traverse.go:70.3,70.21 1 0
github.com/IBM/fp-go/v2/idiomatic/result/validation.go:84.81,85.54 1 0
github.com/IBM/fp-go/v2/idiomatic/result/validation.go:85.54,86.55 1 0
github.com/IBM/fp-go/v2/idiomatic/result/validation.go:86.55,88.19 1 0
github.com/IBM/fp-go/v2/idiomatic/result/validation.go:88.19,89.22 1 0
github.com/IBM/fp-go/v2/idiomatic/result/validation.go:89.22,91.6 1 0
github.com/IBM/fp-go/v2/idiomatic/result/validation.go:93.5,93.25 1 0
github.com/IBM/fp-go/v2/idiomatic/result/validation.go:96.4,96.21 1 0
github.com/IBM/fp-go/v2/idiomatic/result/validation.go:96.21,98.5 1 0
github.com/IBM/fp-go/v2/idiomatic/result/validation.go:100.4,100.21 1 0

View File

@@ -0,0 +1,127 @@
// Copyright (c) 2023 - 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 result
// Curry0 converts a Go function that returns (R, error) into a curried version that returns (R, error).
//
// Example:
//
// getConfig := func() (string, error) { return "config", nil }
// curried := either.Curry0(getConfig)
// result := curried() // Right("config")
func Curry0[R any](f func() (R, error)) func() (R, error) {
return f
}
// Curry1 converts a Go function that returns (R, error) into a curried version that returns (R, error).
//
// Example:
//
// parse := func(s string) (int, error) { return strconv.Atoi(s) }
// curried := either.Curry1(parse)
// result := curried("42") // Right(42)
func Curry1[T1, R any](f func(T1) (R, error)) func(T1) (R, error) {
return f
}
// Curry2 converts a 2-argument Go function that returns (R, error) into a curried version.
//
// Example:
//
// divide := func(a, b int) (int, error) {
// if b == 0 { return 0, errors.New("div by zero") }
// return a / b, nil
// }
// curried := either.Curry2(divide)
// result := curried(10)(2) // Right(5)
func Curry2[T1, T2, R any](f func(T1, T2) (R, error)) func(T1) func(T2) (R, error) {
return func(t1 T1) func(T2) (R, error) {
return func(t2 T2) (R, error) {
return f(t1, t2)
}
}
}
// Curry3 converts a 3-argument Go function that returns (R, error) into a curried version.
func Curry3[T1, T2, T3, R any](f func(T1, T2, T3) (R, error)) func(T1) func(T2) func(T3) (R, error) {
return func(t1 T1) func(T2) func(T3) (R, error) {
return func(t2 T2) func(T3) (R, error) {
return func(t3 T3) (R, error) {
return f(t1, t2, t3)
}
}
}
}
// Curry4 converts a 4-argument Go function that returns (R, error) into a curried version.
func Curry4[T1, T2, T3, T4, R any](f func(T1, T2, T3, T4) (R, error)) func(T1) func(T2) func(T3) func(T4) (R, error) {
return func(t1 T1) func(T2) func(T3) func(T4) (R, error) {
return func(t2 T2) func(T3) func(T4) (R, error) {
return func(t3 T3) func(T4) (R, error) {
return func(t4 T4) (R, error) {
return f(t1, t2, t3, t4)
}
}
}
}
}
// Uncurry0 converts a function returning (R, error) back to Go's (R, error) style.
//
// Example:
//
// curried := func() either.Either[error, string] { return either.Right[error]("value") }
// uncurried := either.Uncurry0(curried)
// result, err := uncurried() // "value", nil
func Uncurry0[R any](f func() (R, error)) func() (R, error) {
return func() (R, error) {
return f()
}
}
// Uncurry1 converts a function returning (R, error) back to Go's (R, error) style.
//
// Example:
//
// curried := func(x int) either.Either[error, string] { return either.Right[error](strconv.Itoa(x)) }
// uncurried := either.Uncurry1(curried)
// result, err := uncurried(42) // "42", nil
func Uncurry1[T1, R any](f func(T1) (R, error)) func(T1) (R, error) {
return func(t1 T1) (R, error) {
return f(t1)
}
}
// Uncurry2 converts a curried function returning (R, error) back to Go's (R, error) style.
func Uncurry2[T1, T2, R any](f func(T1) func(T2) (R, error)) func(T1, T2) (R, error) {
return func(t1 T1, t2 T2) (R, error) {
return f(t1)(t2)
}
}
// Uncurry3 converts a curried function returning (R, error) back to Go's (R, error) style.
func Uncurry3[T1, T2, T3, R any](f func(T1) func(T2) func(T3) (R, error)) func(T1, T2, T3) (R, error) {
return func(t1 T1, t2 T2, t3 T3) (R, error) {
return f(t1)(t2)(t3)
}
}
// Uncurry4 converts a curried function returning (R, error) back to Go's (R, error) style.
func Uncurry4[T1, T2, T3, T4, R any](f func(T1) func(T2) func(T3) func(T4) (R, error)) func(T1, T2, T3, T4) (R, error) {
return func(t1 T1, t2 T2, t3 T3, t4 T4) (R, error) {
return f(t1)(t2)(t3)(t4)
}
}

View File

@@ -0,0 +1,66 @@
// Copyright (c) 2023 - 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 result provides an idiomatic Go approach to error handling using the (value, error) tuple pattern.
//
// This package represents the Result/Either monad idiomatically in Go, leveraging the standard
// (T, error) return pattern that Go developers are familiar with. By convention:
// - (value, nil) represents a success case (Right)
// - (zero, error) represents a failure case (Left)
//
// # Core Concepts
//
// The Result pattern is a functional approach to error handling that makes error flows explicit
// and composable. Instead of checking errors manually at each step, you can chain operations
// that automatically short-circuit on the first error.
//
// # Basic Usage
//
// // Creating Result values
// success := result.Right[error](42) // (42, nil)
// failure := result.Left[int](errors.New("oops")) // (0, error)
//
// // Pattern matching with Fold
// output := result.Fold(
// func(err error) string { return "Error: " + err.Error() },
// func(n int) string { return fmt.Sprintf("Success: %d", n) },
// )(success)
//
// // Chaining operations (short-circuits on Left/error)
// output := result.Chain(func(n int) (int, error) {
// return result.Right[error](n * 2)
// })(success)
//
// # Monadic Operations
//
// Result implements the Monad interface, providing:
// - Map: Transform the Right value
// - Chain (FlatMap): Chain computations that may fail
// - Ap: Apply a function wrapped in Result
//
// # Error Handling
//
// Result provides utilities for working with Go's error type:
// - FromError: Create Result from error-returning functions
// - FromPredicate: Create Result based on a predicate
// - ToError: Extract the error from a Result
//
// # Subpackages
//
// - result/exec: Execute system commands returning Result
// - result/http: HTTP request builders returning Result
package result
//go:generate go run .. either --count 15 --filename gen.go

View File

@@ -0,0 +1,341 @@
// Copyright (c) 2023 - 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 result
import (
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/idiomatic/option"
)
// Of constructs a Right value containing the given value.
// This is the monadic return/pure operation for Either.
// Equivalent to [Right].
//
// Example:
//
// result := either.Of[error](42) // Right(42)
//
//go:inline
func Of[A any](a A) (A, error) {
return a, nil
}
// Ap is the curried version of [MonadAp].
// Returns a function that applies a wrapped function to the given wrapped value.
func Ap[B, A any](fa A, faerr error) Operator[func(A) B, B] {
return func(fab func(A) B, faberr error) (B, error) {
if faberr != nil {
return Left[B](faberr)
}
if faerr != nil {
return Left[B](faerr)
}
return Of(fab(fa))
}
}
// BiMap is the curried version of [MonadBiMap].
// Maps a pair of functions over the two type arguments of the bifunctor.
func BiMap[A, B any](f Endomorphism[error], g func(a A) B) Operator[A, B] {
return func(a A, err error) (B, error) {
if err != nil {
return Left[B](f(err))
}
return Of(g(a))
}
}
// MapTo is the curried version of [MonadMapTo].
func MapTo[A, B any](b B) Operator[A, B] {
return func(_ A, err error) (B, error) {
return b, err
}
}
// Map is the curried version of [MonadMap].
// Transforms the Right value using the provided function.
func Map[A, B any](f func(A) B) Operator[A, B] {
return func(a A, err error) (B, error) {
if err != nil {
return Left[B](err)
}
return Of(f(a))
}
}
// MapLeft is the curried version of [MonadMapLeft].
// Applies a mapping function to the Left (error) channel.
func MapLeft[A any](f Endomorphism[error]) Operator[A, A] {
return func(a A, err error) (A, error) {
if err != nil {
return Left[A](f(err))
}
return Of(a)
}
}
// ChainOptionK is the curried version of [MonadChainOptionK].
func ChainOptionK[A, B any](onNone func() error) func(option.Kleisli[A, B]) Operator[A, B] {
return func(f func(A) (B, bool)) Operator[A, B] {
return func(a A, err error) (B, error) {
if err != nil {
return Left[B](err)
}
if b, ok := f(a); ok {
return Of(b)
}
return Left[B](onNone())
}
}
}
// ChainTo is the curried version of [MonadChainTo].
func ChainTo[A, B any](b B, berr error) Operator[A, B] {
if berr != nil {
return func(_ A, _ error) (B, error) {
return Left[B](berr)
}
}
return func(a A, aerr error) (B, error) {
if aerr != nil {
return Left[B](aerr)
}
return Of(b)
}
}
// Chain is the curried version of [MonadChain].
// Sequences two computations where the second depends on the first.
func Chain[A, B any](f Kleisli[A, B]) Operator[A, B] {
return func(a A, aerr error) (B, error) {
if aerr != nil {
return Left[B](aerr)
}
return f(a)
}
}
// ChainFirst is the curried version of [MonadChainFirst].
func ChainFirst[A, B any](f Kleisli[A, B]) Operator[A, A] {
return func(a A, aerr error) (A, error) {
if aerr != nil {
return Left[A](aerr)
}
_, berr := f(a)
return a, berr
}
}
// FromOption converts an Option to an Either, using the provided function to generate a Left value for None.
//
// Example:
//
// opt := option.Some(42)
// result := either.FromOption[int](func() error { return errors.New("none") })(opt) // Right(42)
func FromOption[A any](onNone func() error) func(A, bool) (A, error) {
return func(a A, aok bool) (A, error) {
if !aok {
return Left[A](onNone())
}
return Of(a)
}
}
// ToOption converts an Either to an Option, discarding the Left value.
//
// Example:
//
// result := either.ToOption(either.Right[error](42)) // Some(42)
// result := either.ToOption(either.Left[int](errors.New("err"))) // None
//
//go:inline
func ToOption[A any](a A, aerr error) (A, bool) {
return a, aerr == nil
}
// FromError creates an Either from a function that may return an error.
//
// Example:
//
// validate := func(x int) error {
// if x < 0 { return errors.New("negative") }
// return nil
// }
// toEither := either.FromError(validate)
// result := toEither(42) // Right(42)
func FromError[A any](f func(a A) error) Kleisli[A, A] {
return func(a A) (A, error) {
return a, f(a)
}
}
// ToError converts an Either[error, A] to an error, returning nil for Right values.
//
// Example:
//
// err := either.ToError(either.Left[int](errors.New("fail"))) // error
// err := either.ToError(either.Right[error](42)) // nil
func ToError[A any](_ A, err error) error {
return err
}
// Fold is the curried version of [MonadFold].
// Extracts the value from an Either by providing handlers for both cases.
//
// Example:
//
// result := either.Fold(
// func(err error) string { return "Error: " + err.Error() },
// func(n int) string { return fmt.Sprintf("Value: %d", n) },
// )(either.Right[error](42)) // "Value: 42"
func Fold[A, B any](onLeft func(error) B, onRight func(A) B) func(A, error) B {
return func(a A, aerr error) B {
if aerr != nil {
return onLeft(aerr)
}
return onRight(a)
}
}
// FromPredicate creates an Either based on a predicate.
// If the predicate returns true, creates a Right; otherwise creates a Left using onFalse.
//
// Example:
//
// isPositive := either.FromPredicate(
// func(x int) bool { return x > 0 },
// func(x int) error { return errors.New("not positive") },
// )
// result := isPositive(42) // Right(42)
// result := isPositive(-1) // Left(error)
func FromPredicate[A any](pred func(A) bool, onFalse func(A) error) Kleisli[A, A] {
return func(a A) (A, error) {
if pred(a) {
return Right(a)
}
return Left[A](onFalse(a))
}
}
// FromNillable creates an Either from a pointer, using the provided error for nil pointers.
//
// Example:
//
// var ptr *int = nil
// result := either.FromNillable[int](errors.New("nil"))(ptr) // Left(error)
// val := 42
// result := either.FromNillable[int](errors.New("nil"))(&val) // Right(&42)
func FromNillable[A any](e error) Kleisli[*A, *A] {
return func(a *A) (*A, error) {
if F.IsNil(a) {
return Left[*A](e)
}
return Of(a)
}
}
// GetOrElse extracts the Right value or computes a default from the Left value.
//
// Example:
//
// result := either.GetOrElse(func(err error) int { return 0 })(either.Right[error](42)) // 42
// result := either.GetOrElse(func(err error) int { return 0 })(either.Left[int](err)) // 0
func GetOrElse[A any](onLeft func(error) A) func(A, error) A {
return func(a A, err error) A {
if err != nil {
return onLeft(err)
}
return a
}
}
// Reduce folds an Either into a single value using a reducer function.
// Returns the initial value for Left, or applies the reducer to the Right value.
func Reduce[A, B any](f func(B, A) B, initial B) func(A, error) B {
return func(a A, err error) B {
if err != nil {
return initial
}
return f(initial, a)
}
}
// Alt provides an alternative Either if the first is Left.
//
// Example:
//
// alternative := either.Alt[error](func() either.Either[error, int] {
// return either.Right[error](99)
// })
// result := alternative(either.Left[int](errors.New("fail"))) // Right(99)
func Alt[A any](that func() (A, error)) Operator[A, A] {
return func(a A, err error) (A, error) {
if err != nil {
return that()
}
return Of(a)
}
}
// OrElse recovers from a Left by providing an alternative computation.
//
// Example:
//
// recover := either.OrElse(func(err error) either.Either[error, int] {
// return either.Right[error](0) // default value
// })
// result := recover(either.Left[int](errors.New("fail"))) // Right(0)
func OrElse[A any](onLeft Kleisli[error, A]) Operator[A, A] {
return func(a A, err error) (A, error) {
if err != nil {
return onLeft(err)
}
return Of(a)
}
}
// ToType attempts to convert an any value to a specific type, returning Either.
//
// Example:
//
// convert := either.ToType[int](func(v any) error {
// return fmt.Errorf("cannot convert %v to int", v)
// })
// result := convert(42) // Right(42)
// result := convert("string") // Left(error)
func ToType[A any](onError func(any) error) Kleisli[any, A] {
return func(x any) (A, error) {
if a, ok := x.(A); ok {
return Of(a)
}
return Left[A](onError(x))
}
}
// Memoize returns the Either unchanged (Either values are already memoized).
func Memoize[A any](a A, err error) (A, error) {
return a, err
}
// Flap is the curried version of [MonadFlap].
func Flap[B, A any](a A) Operator[func(A) B, B] {
return func(fab func(A) B, faberr error) (B, error) {
if faberr != nil {
return Left[B](faberr)
}
return Of(fab(a))
}
}

View File

@@ -0,0 +1,566 @@
// Copyright (c) 2023 - 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 result
import (
"errors"
"testing"
N "github.com/IBM/fp-go/v2/number"
)
var (
errBench = errors.New("benchmark error")
benchResultInt int
benchResultErr error
benchBool bool
benchInt int
benchString string
)
// Benchmark core constructors
func BenchmarkLeft(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = Left[int](errBench)
}
}
func BenchmarkRight(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = Right(42)
}
}
func BenchmarkOf(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = Of(42)
}
}
// Benchmark predicates
func BenchmarkIsLeft(b *testing.B) {
val, err := Left[int](errBench)
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchBool = IsLeft(val, err)
}
}
func BenchmarkIsRight(b *testing.B) {
val, err := Right(42)
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchBool = IsRight(val, err)
}
}
// Benchmark fold operations
func BenchmarkFold_Right(b *testing.B) {
val, err := Right(42)
folder := Fold(
func(e error) int { return 0 },
N.Mul(2),
)
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchInt = folder(val, err)
}
}
func BenchmarkFold_Left(b *testing.B) {
val, err := Left[int](errBench)
folder := Fold(
func(e error) int { return 0 },
N.Mul(2),
)
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchInt = folder(val, err)
}
}
// Benchmark functor operations
func BenchmarkMap_Right(b *testing.B) {
val, err := Right(42)
mapper := Map(N.Mul(2))
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = mapper(val, err)
}
}
func BenchmarkMap_Left(b *testing.B) {
val, err := Left[int](errBench)
mapper := Map(N.Mul(2))
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = mapper(val, err)
}
}
// Benchmark monad operations
func BenchmarkChain_Right(b *testing.B) {
val, err := Right(42)
chainer := Chain(func(a int) (int, error) { return Right(a * 2) })
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = chainer(val, err)
}
}
func BenchmarkChain_Left(b *testing.B) {
val, err := Left[int](errBench)
chainer := Chain(func(a int) (int, error) { return Right(a * 2) })
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = chainer(val, err)
}
}
func BenchmarkChainFirst_Right(b *testing.B) {
val, err := Right(42)
chainer := ChainFirst(func(a int) (string, error) { return Right("logged") })
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = chainer(val, err)
}
}
func BenchmarkChainFirst_Left(b *testing.B) {
val, err := Left[int](errBench)
chainer := ChainFirst(func(a int) (string, error) { return Right("logged") })
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = chainer(val, err)
}
}
// Benchmark alternative operations
func BenchmarkAlt_RightRight(b *testing.B) {
val, err := Right(42)
alternative := Alt(func() (int, error) { return Right(99) })
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = alternative(val, err)
}
}
func BenchmarkAlt_LeftRight(b *testing.B) {
val, err := Left[int](errBench)
alternative := Alt(func() (int, error) { return Right(99) })
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = alternative(val, err)
}
}
func BenchmarkOrElse_Right(b *testing.B) {
val, err := Right(42)
recover := OrElse(func(e error) (int, error) { return Right(0) })
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = recover(val, err)
}
}
func BenchmarkOrElse_Left(b *testing.B) {
val, err := Left[int](errBench)
recover := OrElse(func(e error) (int, error) { return Right(0) })
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = recover(val, err)
}
}
// Benchmark GetOrElse
func BenchmarkGetOrElse_Right(b *testing.B) {
val, err := Right(42)
getter := GetOrElse(func(e error) int { return 0 })
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchInt = getter(val, err)
}
}
func BenchmarkGetOrElse_Left(b *testing.B) {
val, err := Left[int](errBench)
getter := GetOrElse(func(e error) int { return 0 })
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchInt = getter(val, err)
}
}
// Benchmark pipeline operations
func BenchmarkPipeline_Map_Right(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = Pipe2(
21,
Right[int],
Map(N.Mul(2)),
)
}
}
func BenchmarkPipeline_Map_Left(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = Pipe2(
0,
func(int) (int, error) { return Left[int](errBench) },
Map(N.Mul(2)),
)
}
}
func BenchmarkPipeline_Chain_Right(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = Pipe2(
21,
Right[int],
Chain(func(x int) (int, error) { return Right(x * 2) }),
)
}
}
func BenchmarkPipeline_Chain_Left(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = Pipe2(
0,
func(int) (int, error) { return Left[int](errBench) },
Chain(func(x int) (int, error) { return Right(x * 2) }),
)
}
}
func BenchmarkPipeline_Complex_Right(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = Pipe4(
10,
Right[int],
Map(N.Mul(2)),
Chain(func(x int) (int, error) { return Right(x + 1) }),
Map(N.Mul(2)),
)
}
}
func BenchmarkPipeline_Complex_Left(b *testing.B) {
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = Pipe4(
0,
func(int) (int, error) { return Left[int](errBench) },
Map(N.Mul(2)),
Chain(func(x int) (int, error) { return Right(x + 1) }),
Map(N.Mul(2)),
)
}
}
// Benchmark string formatting
func BenchmarkToString_Right(b *testing.B) {
val, err := Right(42)
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchString = ToString(val, err)
}
}
func BenchmarkToString_Left(b *testing.B) {
val, err := Left[int](errBench)
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchString = ToString(val, err)
}
}
// Benchmark BiMap
func BenchmarkBiMap_Right(b *testing.B) {
val, err := Right(42)
wrapErr := func(e error) error { return e }
mapper := BiMap[int, int](wrapErr, N.Mul(2))
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = mapper(val, err)
}
}
func BenchmarkBiMap_Left(b *testing.B) {
val, err := Left[int](errBench)
wrapErr := func(e error) error { return e }
mapper := BiMap[int, int](wrapErr, N.Mul(2))
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = mapper(val, err)
}
}
// Benchmark MapTo
func BenchmarkMapTo_Right(b *testing.B) {
val, err := Right(42)
mapper := MapTo[int, int](99)
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = mapper(val, err)
}
}
func BenchmarkMapTo_Left(b *testing.B) {
val, err := Left[int](errBench)
mapper := MapTo[int, int](99)
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = mapper(val, err)
}
}
// Benchmark MapLeft
func BenchmarkMapLeft_Right(b *testing.B) {
val, err := Right(42)
mapper := MapLeft[int](func(e error) error { return e })
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = mapper(val, err)
}
}
func BenchmarkMapLeft_Left(b *testing.B) {
val, err := Left[int](errBench)
mapper := MapLeft[int](func(e error) error { return e })
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = mapper(val, err)
}
}
// Benchmark ChainTo
func BenchmarkChainTo_Right(b *testing.B) {
val, err := Right(42)
chainer := ChainTo[int, int](99, nil)
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = chainer(val, err)
}
}
func BenchmarkChainTo_Left(b *testing.B) {
val, err := Left[int](errBench)
chainer := ChainTo[int, int](99, nil)
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = chainer(val, err)
}
}
// Benchmark Reduce
func BenchmarkReduce_Right(b *testing.B) {
val, err := Right(42)
reducer := Reduce(func(acc, v int) int { return acc + v }, 10)
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchInt = reducer(val, err)
}
}
func BenchmarkReduce_Left(b *testing.B) {
val, err := Left[int](errBench)
reducer := Reduce(func(acc, v int) int { return acc + v }, 10)
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchInt = reducer(val, err)
}
}
// Benchmark FromPredicate
func BenchmarkFromPredicate_Pass(b *testing.B) {
pred := FromPredicate(
func(x int) bool { return x > 0 },
func(x int) error { return errBench },
)
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = pred(42)
}
}
func BenchmarkFromPredicate_Fail(b *testing.B) {
pred := FromPredicate(
func(x int) bool { return x > 0 },
func(x int) error { return errBench },
)
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = pred(-1)
}
}
// Benchmark Flap
func BenchmarkFlap_Right(b *testing.B) {
fn, ferr := Right(N.Mul(2))
flapper := Flap[int, int](21)
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = flapper(fn, ferr)
}
}
func BenchmarkFlap_Left(b *testing.B) {
fn, ferr := Left[func(int) int](errBench)
flapper := Flap[int, int](21)
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = flapper(fn, ferr)
}
}
// Benchmark ToOption
func BenchmarkToOption_Right(b *testing.B) {
val, err := Right(42)
var resVal int
var resOk bool
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
resVal, resOk = ToOption(val, err)
benchInt = resVal
benchBool = resOk
}
}
func BenchmarkToOption_Left(b *testing.B) {
val, err := Left[int](errBench)
var resVal int
var resOk bool
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
resVal, resOk = ToOption(val, err)
benchInt = resVal
benchBool = resOk
}
}
// Benchmark FromOption
func BenchmarkFromOption_Some(b *testing.B) {
converter := FromOption[int](func() error { return errBench })
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = converter(42, true)
}
}
func BenchmarkFromOption_None(b *testing.B) {
converter := FromOption[int](func() error { return errBench })
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultInt, benchResultErr = converter(0, false)
}
}
// Benchmark ToError
func BenchmarkToError_Right(b *testing.B) {
val, err := Right(42)
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultErr = ToError(val, err)
}
}
func BenchmarkToError_Left(b *testing.B) {
val, err := Left[int](errBench)
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
benchResultErr = ToError(val, err)
}
}
// Benchmark TraverseArray
func BenchmarkTraverseArray_Success(b *testing.B) {
input := []int{1, 2, 3, 4, 5}
traverse := TraverseArray(func(x int) (int, error) {
return Right(x * 2)
})
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
_, benchResultErr = traverse(input)
}
}
func BenchmarkTraverseArray_Error(b *testing.B) {
input := []int{1, 2, 3, 4, 5}
traverse := TraverseArray(func(x int) (int, error) {
if x == 3 {
return Left[int](errBench)
}
return Right(x * 2)
})
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
_, benchResultErr = traverse(input)
}
}

View File

@@ -0,0 +1,112 @@
// Copyright (c) 2023 - 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 result
import (
"errors"
"testing"
F "github.com/IBM/fp-go/v2/function"
"github.com/IBM/fp-go/v2/idiomatic/option"
"github.com/IBM/fp-go/v2/internal/utils"
S "github.com/IBM/fp-go/v2/string"
"github.com/stretchr/testify/assert"
)
func TestIsLeft(t *testing.T) {
err := errors.New("Some error")
withError, e1 := Left[string](err)
assert.True(t, IsLeft(withError, e1))
assert.False(t, IsRight(withError, e1))
}
func TestIsRight(t *testing.T) {
noError, err := Right("Carsten")
assert.True(t, IsRight(noError, err))
assert.False(t, IsLeft(noError, err))
}
func TestMapEither(t *testing.T) {
AssertEq(Pipe2("abc", Of, Map(utils.StringLen)))(Right(3))(t)
e := errors.New("s")
AssertEq(Left[int](e))(Pipe2(e, Left[string], Map(utils.StringLen)))(t)
}
func TestAp(t *testing.T) {
f := S.Size
maError := errors.New("maError")
mabError := errors.New("mabError")
AssertEq(Right(3))(Pipe2(f, Right, Ap[int](Right("abc"))))(t)
AssertEq(Left[int](maError))(Pipe2(f, Right, Ap[int](Left[string](maError))))(t)
AssertEq(Left[int](mabError))(Pipe2(mabError, Left[func(string) int], Ap[int](Left[string](maError))))(t)
AssertEq(Left[int](mabError))(Pipe2(mabError, Left[func(string) int], Ap[int](Right("abc"))))(t)
}
func TestAlt(t *testing.T) {
a := errors.New("a")
b := errors.New("b")
AssertEq(Right(1))(Pipe2(1, Right, Alt(func() (int, error) { return Right(2) })))(t)
AssertEq(Right(1))(Pipe2(1, Right, Alt(func() (int, error) { return Left[int](a) })))(t)
AssertEq(Right(2))(Pipe2(b, Left[int], Alt(func() (int, error) { return Right(2) })))(t)
AssertEq(Left[int](b))(Pipe2(a, Left[int], Alt(func() (int, error) { return Left[int](b) })))(t)
}
func TestChainFirst(t *testing.T) {
f := func(s string) (int, error) {
return Of(S.Size((s)))
}
maError := errors.New("maError")
AssertEq(Right("abc"))(Pipe2("abc", Right, ChainFirst(f)))(t)
AssertEq(Left[string](maError))(Pipe2(maError, Left[string], ChainFirst(f)))(t)
}
func TestChainOptionK(t *testing.T) {
a := errors.New("a")
b := errors.New("b")
f := ChainOptionK[int, int](F.Constant(a))(func(n int) (int, bool) {
if n > 0 {
return option.Some(n)
}
return option.None[int]()
})
AssertEq(Right(1))(f(Right(1)))(t)
AssertEq(Left[int](a))(f(Right(-1)))(t)
AssertEq(Left[int](b))(f(Left[int](b)))(t)
}
func TestFromOption(t *testing.T) {
none := errors.New("none")
AssertEq(Left[int](none))(FromOption[int](F.Constant(none))(option.None[int]()))(t)
AssertEq(Right(1))(FromOption[int](F.Constant(none))(option.Some(1)))(t)
}
func TestStringer(t *testing.T) {
e := ToString(Of("foo"))
exp := "Right[string](foo)"
assert.Equal(t, exp, e)
}

63
v2/idiomatic/result/eq.go Normal file
View File

@@ -0,0 +1,63 @@
// Copyright (c) 2023 - 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 result
import (
EQ "github.com/IBM/fp-go/v2/eq"
)
// Eq constructs an equality predicate for Result values (A, error).
// Two Result values are equal if they are both Left (error) with equal error values,
// or both Right (success) with equal values according to the provided equality predicate.
//
// Parameters:
// - eq: Equality predicate for the Right (success) type A
//
// Returns a curried comparison function that takes two Result values and returns true if equal.
//
// Example:
//
// eq := result.Eq(eq.FromStrictEquals[int]())
// result1 := eq(42, nil)(42, nil) // true
// result2 := eq(42, nil)(43, nil) // false
func Eq[A any](eq EQ.Eq[A]) func(A, error) func(A, error) bool {
return func(a A, aerr error) func(A, error) bool {
return func(b A, berr error) bool {
if aerr != nil {
if berr != nil {
return aerr == berr
}
return false
}
if berr != nil {
return false
}
return eq.Equals(a, b)
}
}
}
// FromStrictEquals constructs an equality predicate using Go's == operator.
// The Right type must be comparable.
//
// Example:
//
// eq := result.FromStrictEquals[int]()
// result1 := eq(42, nil)(42, nil) // true
// result2 := eq(42, nil)(43, nil) // false
func FromStrictEquals[A comparable]() func(A, error) func(A, error) bool {
return Eq(EQ.FromStrictEquals[A]())
}

View File

@@ -0,0 +1,54 @@
// Copyright (c) 2023 - 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 result
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestEq(t *testing.T) {
r1v, r1e := Of(1)
r2v, r2e := Of(2)
e1v, e1e := Left[int](errorString("a"))
e2v, e2e := Left[int](errorString("a"))
e3v, e3e := Left[int](errorString("b"))
eq := FromStrictEquals[int]()
// Right values
assert.True(t, eq(r1v, r1e)(r1v, r1e))
assert.False(t, eq(r1v, r1e)(r2v, r2e))
// Left values (errors)
assert.True(t, eq(e1v, e1e)(e1v, e1e))
assert.True(t, eq(e1v, e1e)(e2v, e2e))
assert.False(t, eq(e1v, e1e)(e3v, e3e))
// Mixed Left and Right
assert.False(t, eq(r1v, r1e)(e1v, e1e))
assert.False(t, eq(e1v, e1e)(r1v, r1e))
}
// errorString is a simple error type for testing
type errorString string
func (e errorString) Error() string {
return string(e)
}

View File

@@ -0,0 +1,58 @@
// Copyright (c) 2023 - 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 result
import (
"fmt"
"github.com/IBM/fp-go/v2/errors"
)
func Example_creation() {
// Build an Either
leftValue, leftErr := Left[string](fmt.Errorf("some error"))
rightValue, rightErr := Right("value")
// Build from a value
fromNillable := FromNillable[string](fmt.Errorf("value was nil"))
leftFromNil, nilErr := fromNillable(nil)
value := "value"
rightFromPointer, ptrErr := fromNillable(&value)
// some predicate
isEven := func(num int) bool {
return num%2 == 0
}
fromEven := FromPredicate(isEven, errors.OnSome[int]("%d is an odd number"))
leftFromPred, predErrOdd := fromEven(3)
rightFromPred, predErrEven := fromEven(4)
fmt.Println(ToString(leftValue, leftErr))
fmt.Println(ToString(rightValue, rightErr))
fmt.Println(ToString(leftFromNil, nilErr))
fmt.Println(IsRight(rightFromPointer, ptrErr))
fmt.Println(ToString(leftFromPred, predErrOdd))
fmt.Println(ToString(rightFromPred, predErrEven))
// Output:
// Left(some error)
// Right[string](value)
// Left(value was nil)
// true
// Left(3 is an odd number)
// Right[int](4)
}

View File

@@ -0,0 +1,62 @@
// Copyright (c) 2023 - 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 result
import (
"fmt"
F "github.com/IBM/fp-go/v2/function"
N "github.com/IBM/fp-go/v2/number"
)
func Example_extraction() {
leftValue, leftErr := Left[int](fmt.Errorf("Division by Zero!"))
rightValue, rightErr := Right(10)
// Convert Either[A] to A with a default value
leftWithDefault := GetOrElse(F.Constant1[error](0))(leftValue, leftErr) // 0
rightWithDefault := GetOrElse(F.Constant1[error](0))(rightValue, rightErr) // 10
// Apply a different function on Left(...)/Right(...)
doubleOrZero := Fold(F.Constant1[error](0), N.Mul(2)) // func(int, error) int
doubleFromLeft := doubleOrZero(leftValue, leftErr) // 0
doubleFromRight := doubleOrZero(rightValue, rightErr) // 20
// You can also chain operations using Map
tripled, tripledErr := Pipe2(
rightValue,
Right[int],
Map(N.Mul(3)),
)
tripledResult := GetOrElse(F.Constant1[error](0))(tripled, tripledErr) // 30
fmt.Println(ToString(leftValue, leftErr))
fmt.Println(ToString(rightValue, rightErr))
fmt.Println(leftWithDefault)
fmt.Println(rightWithDefault)
fmt.Println(doubleFromLeft)
fmt.Println(doubleFromRight)
fmt.Println(tripledResult)
// Output:
// Left(Division by Zero!)
// Right[int](10)
// 0
// 10
// 0
// 20
// 30
}

View File

@@ -0,0 +1,49 @@
// Copyright (c) 2023 - 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 exec provides utilities for executing system commands with Either-based error handling.
package exec
import (
"context"
"github.com/IBM/fp-go/v2/exec"
"github.com/IBM/fp-go/v2/idiomatic/result"
GE "github.com/IBM/fp-go/v2/internal/exec"
)
var (
// Command executes a system command and returns the result as an Either.
// Use this version if the command does not produce any side effects,
// i.e., if the output is uniquely determined by the input.
// For commands with side effects, typically you'd use the IOEither version instead.
//
// Parameters (curried):
// - name: The command name/path
// - args: Command arguments
// - in: Input bytes to send to the command's stdin
//
// Returns Either[error, CommandOutput] containing the command's output or an error.
//
// Example:
//
// result := exec.Command("echo")( []string{"hello"})([]byte{})
// // result is Right(CommandOutput{Stdout: "hello\n", ...})
Command = result.Curry3(command)
)
func command(name string, args []string, in []byte) (exec.CommandOutput, error) {
return GE.Exec(context.Background(), name, args, in)
}

View File

@@ -0,0 +1,89 @@
package result
// Pipe1 takes an initial value t0 and successively applies 1 functions where the input of a function is the return value of the previous function
// The final return value is the result of the last function application
//
//go:inline
func Pipe1[F1 ~func(T0) (T1, error), T0, T1 any](t0 T0, f1 F1) (T1, error) {
return f1(t0)
}
// Flow1 creates a function that takes an initial value t0 and successively applies 1 functions where the input of a function is the return value of the previous function
// The final return value is the result of the last function application
//
//go:inline
func Flow1[F1 ~func(T0, error) (T1, error), T0, T1 any](f1 F1) func(T0, error) (T1, error) {
return f1
}
// Pipe2 takes an initial value t0 and successively applies 2 functions where the input of a function is the return value of the previous function
// The final return value is the result of the last function application
//
//go:inline
func Pipe2[F1 ~func(T0) (T1, error), F2 ~func(T1, error) (T2, error), T0, T1, T2 any](t0 T0, f1 F1, f2 F2) (T2, error) {
return f2(f1(t0))
}
// Flow2 creates a function that takes an initial value t0 and successively applies 2 functions where the input of a function is the return value of the previous function
// The final return value is the result of the last function application
//
//go:inline
func Flow2[F1 ~func(T0, error) (T1, error), F2 ~func(T1, error) (T2, error), T0, T1, T2 any](f1 F1, f2 F2) func(T0, error) (T2, error) {
return func(t0 T0, t0ok error) (T2, error) {
return f2(f1(t0, t0ok))
}
}
// Pipe3 takes an initial value t0 and successively applies 3 functions where the input of a function is the return value of the previous function
// The final return value is the result of the last function application
//
//go:inline
func Pipe3[F1 ~func(T0) (T1, error), F2 ~func(T1, error) (T2, error), F3 ~func(T2, error) (T3, error), T0, T1, T2, T3 any](t0 T0, f1 F1, f2 F2, f3 F3) (T3, error) {
return f3(f2(f1(t0)))
}
// Flow3 creates a function that takes an initial value t0 and successively applies 3 functions where the input of a function is the return value of the previous function
// The final return value is the result of the last function application
//
//go:inline
func Flow3[F1 ~func(T0, error) (T1, error), F2 ~func(T1, error) (T2, error), F3 ~func(T2, error) (T3, error), T0, T1, T2, T3 any](f1 F1, f2 F2, f3 F3) func(T0, error) (T3, error) {
return func(t0 T0, t0ok error) (T3, error) {
return f3(f2(f1(t0, t0ok)))
}
}
// Pipe4 takes an initial value t0 and successively applies 4 functions where the input of a function is the return value of the previous function
// The final return value is the result of the last function application
//
//go:inline
func Pipe4[F1 ~func(T0) (T1, error), F2 ~func(T1, error) (T2, error), F3 ~func(T2, error) (T3, error), F4 ~func(T3, error) (T4, error), T0, T1, T2, T3, T4 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4) (T4, error) {
return f4(f3(f2(f1(t0))))
}
// Flow4 creates a function that takes an initial value t0 and successively applies 4 functions where the input of a function is the return value of the previous function
// The final return value is the result of the last function application
//
//go:inline
func Flow4[F1 ~func(T0, error) (T1, error), F2 ~func(T1, error) (T2, error), F3 ~func(T2, error) (T3, error), F4 ~func(T3, error) (T4, error), T0, T1, T2, T3, T4 any](f1 F1, f2 F2, f3 F3, f4 F4) func(T0, error) (T4, error) {
return func(t0 T0, t0ok error) (T4, error) {
return f4(f3(f2(f1(t0, t0ok))))
}
}
// Pipe5 takes an initial value t0 and successively applies 5 functions where the input of a function is the return value of the previous function
// The final return value is the result of the last function application
//
//go:inline
func Pipe5[F1 ~func(T0) (T1, error), F2 ~func(T1, error) (T2, error), F3 ~func(T2, error) (T3, error), F4 ~func(T3, error) (T4, error), F5 ~func(T4, error) (T5, error), T0, T1, T2, T3, T4, T5 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) (T5, error) {
return f5(f4(f3(f2(f1(t0)))))
}
// Flow5 creates a function that takes an initial value t0 and successively applies 5 functions where the input of a function is the return value of the previous function
// The final return value is the result of the last function application
//
//go:inline
func Flow5[F1 ~func(T0, error) (T1, error), F2 ~func(T1, error) (T2, error), F3 ~func(T2, error) (T3, error), F4 ~func(T3, error) (T4, error), F5 ~func(T4, error) (T5, error), T0, T1, T2, T3, T4, T5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(T0, error) (T5, error) {
return func(t0 T0, t0ok error) (T5, error) {
return f5(f4(f3(f2(f1(t0, t0ok)))))
}
}

View File

@@ -0,0 +1,454 @@
// Copyright (c) 2023 - 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 result
import (
"errors"
"fmt"
"testing"
N "github.com/IBM/fp-go/v2/number"
"github.com/stretchr/testify/assert"
)
// TestBiMap tests mapping over both error and value channels
func TestBiMap(t *testing.T) {
wrapError := func(e error) error {
return fmt.Errorf("wrapped: %w", e)
}
double := N.Mul(2)
t.Run("BiMap on Right", func(t *testing.T) {
val, err := BiMap[int, int](wrapError, double)(Right(21))
AssertEq(Right(42))(val, err)(t)
})
t.Run("BiMap on Left", func(t *testing.T) {
originalErr := errors.New("original")
val, err := BiMap[int, int](wrapError, double)(Left[int](originalErr))
assert.Error(t, err)
assert.Contains(t, err.Error(), "wrapped")
assert.Contains(t, err.Error(), "original")
assert.Equal(t, 0, val)
})
}
// TestMapTo tests mapping to a constant value
func TestMapTo(t *testing.T) {
t.Run("MapTo on Right", func(t *testing.T) {
val, err := MapTo[int, string]("constant")(Right(42))
AssertEq(Right("constant"))(val, err)(t)
})
t.Run("MapTo on Left", func(t *testing.T) {
originalErr := errors.New("error")
val, err := MapTo[int, string]("constant")(Left[int](originalErr))
assert.Error(t, err)
assert.Equal(t, originalErr, err)
// MapTo still applies the constant value even for Left
assert.Equal(t, "constant", val)
})
}
// TestChainTo tests chaining to a constant value
func TestChainTo(t *testing.T) {
t.Run("ChainTo Right to Right", func(t *testing.T) {
val, err := ChainTo[int, string]("success", nil)(Right(42))
AssertEq(Right("success"))(val, err)(t)
})
t.Run("ChainTo Right to Left", func(t *testing.T) {
targetErr := errors.New("target error")
val, err := ChainTo[int, string]("", targetErr)(Right(42))
assert.Error(t, err)
assert.Equal(t, targetErr, err)
assert.Equal(t, "", val)
})
t.Run("ChainTo Left", func(t *testing.T) {
sourceErr := errors.New("source error")
val, err := ChainTo[int, string]("success", nil)(Left[int](sourceErr))
assert.Error(t, err)
assert.Equal(t, sourceErr, err)
assert.Equal(t, "", val)
})
}
// TestReduce tests the reduce/fold operation
func TestReduce(t *testing.T) {
sum := func(acc, val int) int {
return acc + val
}
t.Run("Reduce on Right", func(t *testing.T) {
result := Reduce(sum, 10)(Right(32))
assert.Equal(t, 42, result)
})
t.Run("Reduce on Left", func(t *testing.T) {
result := Reduce(sum, 10)(Left[int](errors.New("error")))
assert.Equal(t, 10, result) // Returns initial value
})
}
// TestFromPredicate tests creating Result from a predicate
func TestFromPredicate(t *testing.T) {
isPositive := FromPredicate(
func(x int) bool { return x > 0 },
func(x int) error { return fmt.Errorf("%d is not positive", x) },
)
t.Run("Predicate passes", func(t *testing.T) {
val, err := isPositive(42)
AssertEq(Right(42))(val, err)(t)
})
t.Run("Predicate fails", func(t *testing.T) {
val, err := isPositive(-5)
assert.Error(t, err)
assert.Contains(t, err.Error(), "not positive")
// FromPredicate returns zero value on Left
assert.Equal(t, 0, val)
})
t.Run("Predicate with zero", func(t *testing.T) {
val, err := isPositive(0)
assert.Error(t, err)
assert.Equal(t, 0, val)
})
}
// TestFromNillable tests creating Result from nullable pointers
func TestFromNillable(t *testing.T) {
nilErr := errors.New("value is nil")
fromPtr := FromNillable[int](nilErr)
t.Run("Non-nil pointer", func(t *testing.T) {
value := 42
val, err := fromPtr(&value)
assert.NoError(t, err)
assert.NotNil(t, val)
assert.Equal(t, 42, *val)
})
t.Run("Nil pointer", func(t *testing.T) {
val, err := fromPtr(nil)
assert.Error(t, err)
assert.Equal(t, nilErr, err)
assert.Nil(t, val)
})
}
// TestToType tests type conversion
func TestToType(t *testing.T) {
toInt := ToType[int](func(v any) error {
return fmt.Errorf("cannot convert %T to int", v)
})
t.Run("Correct type", func(t *testing.T) {
val, err := toInt(42)
AssertEq(Right(42))(val, err)(t)
})
t.Run("Wrong type", func(t *testing.T) {
val, err := toInt("string")
assert.Error(t, err)
assert.Contains(t, err.Error(), "cannot convert")
assert.Equal(t, 0, val)
})
t.Run("Nil value", func(t *testing.T) {
val, err := toInt(nil)
assert.Error(t, err)
assert.Equal(t, 0, val)
})
}
// TestMemoize tests that Memoize returns the value unchanged
func TestMemoize(t *testing.T) {
t.Run("Memoize Right", func(t *testing.T) {
val, err := Memoize(Right(42))
AssertEq(Right(42))(val, err)(t)
})
t.Run("Memoize Left", func(t *testing.T) {
originalErr := errors.New("error")
val, err := Memoize(Left[int](originalErr))
assert.Error(t, err)
assert.Equal(t, originalErr, err)
assert.Equal(t, 0, val)
})
}
// TestFlap tests reverse application
func TestFlap(t *testing.T) {
t.Run("Flap with Right function", func(t *testing.T) {
double := N.Mul(2)
val, err := Flap[int, int](21)(Right(double))
AssertEq(Right(42))(val, err)(t)
})
t.Run("Flap with Left function", func(t *testing.T) {
fnErr := errors.New("function error")
val, err := Flap[int, int](21)(Left[func(int) int](fnErr))
assert.Error(t, err)
assert.Equal(t, fnErr, err)
assert.Equal(t, 0, val)
})
}
// TestToError tests extracting error from Result
func TestToError(t *testing.T) {
t.Run("ToError from Right", func(t *testing.T) {
err := ToError(Right(42))
assert.NoError(t, err)
})
t.Run("ToError from Left", func(t *testing.T) {
originalErr := errors.New("error")
err := ToError(Left[int](originalErr))
assert.Error(t, err)
assert.Equal(t, originalErr, err)
})
}
// TestLet tests the Let operation for do-notation
func TestLet(t *testing.T) {
type State struct {
value int
}
t.Run("Let with Right", func(t *testing.T) {
val, err := Pipe2(
State{value: 10},
Right,
Let(
func(v int) func(State) State {
return func(s State) State { return State{value: s.value + v} }
},
func(s State) int { return 32 },
),
)
assert.NoError(t, err)
assert.Equal(t, 42, val.value)
})
t.Run("Let with Left", func(t *testing.T) {
originalErr := errors.New("error")
val, err := Pipe2(
originalErr,
Left[State],
Let(
func(v int) func(State) State {
return func(s State) State { return State{value: v} }
},
func(s State) int { return 42 },
),
)
assert.Error(t, err)
assert.Equal(t, originalErr, err)
assert.Equal(t, State{}, val)
})
}
// TestLetTo tests the LetTo operation
func TestLetTo(t *testing.T) {
type State struct {
name string
}
t.Run("LetTo with Right", func(t *testing.T) {
val, err := Pipe2(
State{},
Right,
LetTo(
func(n string) func(State) State {
return func(s State) State { return State{name: n} }
},
"Alice",
),
)
assert.NoError(t, err)
assert.Equal(t, "Alice", val.name)
})
t.Run("LetTo with Left", func(t *testing.T) {
originalErr := errors.New("error")
val, err := Pipe2(
originalErr,
Left[State],
LetTo(
func(n string) func(State) State {
return func(s State) State { return State{name: n} }
},
"Bob",
),
)
assert.Error(t, err)
assert.Equal(t, State{}, val)
})
}
// TestBindTo tests the BindTo operation
func TestBindTo(t *testing.T) {
type State struct {
value int
}
t.Run("BindTo with Right", func(t *testing.T) {
val, err := Pipe2(
42,
Right,
BindTo(func(v int) State { return State{value: v} }),
)
assert.NoError(t, err)
assert.Equal(t, 42, val.value)
})
t.Run("BindTo with Left", func(t *testing.T) {
originalErr := errors.New("error")
val, err := Pipe2(
originalErr,
Left[int],
BindTo(func(v int) State { return State{value: v} }),
)
assert.Error(t, err)
assert.Equal(t, State{}, val)
})
}
// TestMapLeft tests mapping over the error channel
func TestMapLeft(t *testing.T) {
wrapError := func(e error) error {
return fmt.Errorf("wrapped: %w", e)
}
t.Run("MapLeft on Right", func(t *testing.T) {
val, err := MapLeft[int](wrapError)(Right(42))
AssertEq(Right(42))(val, err)(t)
})
t.Run("MapLeft on Left", func(t *testing.T) {
originalErr := errors.New("original")
_, err := MapLeft[int](wrapError)(Left[int](originalErr))
assert.Error(t, err)
assert.Contains(t, err.Error(), "wrapped")
assert.Contains(t, err.Error(), "original")
})
}
// TestOrElse tests recovery from error
func TestOrElse(t *testing.T) {
recover := OrElse(func(e error) (int, error) {
return Right(0) // default value
})
t.Run("OrElse on Right", func(t *testing.T) {
val, err := recover(Right(42))
AssertEq(Right(42))(val, err)(t)
})
t.Run("OrElse on Left recovers", func(t *testing.T) {
val, err := recover(Left[int](errors.New("error")))
AssertEq(Right(0))(val, err)(t)
})
}
// TestDo tests the Do operation
func TestDo(t *testing.T) {
type State struct {
x int
y int
}
result, err := Do(State{})
assert.NoError(t, err)
assert.Equal(t, State{}, result)
}
// TestOf tests the Of/pure operation
func TestOf(t *testing.T) {
val, err := Of(42)
AssertEq(Right(42))(val, err)(t)
}
// TestToString tests string representation
func TestToString(t *testing.T) {
t.Run("ToString Right", func(t *testing.T) {
str := ToString(Right(42))
assert.Equal(t, "Right[int](42)", str)
})
t.Run("ToString Left", func(t *testing.T) {
str := ToString(Left[int](errors.New("error")))
assert.Contains(t, str, "Left(")
assert.Contains(t, str, "error")
})
}
// TestToOption tests conversion to Option
func TestToOption(t *testing.T) {
t.Run("ToOption from Right", func(t *testing.T) {
val, ok := ToOption(Right(42))
assert.True(t, ok)
assert.Equal(t, 42, val)
})
t.Run("ToOption from Left", func(t *testing.T) {
val, ok := ToOption(Left[int](errors.New("error")))
assert.False(t, ok)
assert.Equal(t, 0, val)
})
}
// TestFromError tests creating Result from error-returning function
func TestFromError(t *testing.T) {
validate := func(x int) error {
if x < 0 {
return errors.New("negative")
}
return nil
}
toResult := FromError(validate)
t.Run("FromError with valid value", func(t *testing.T) {
val, err := toResult(42)
AssertEq(Right(42))(val, err)(t)
})
t.Run("FromError with invalid value", func(t *testing.T) {
val, err := toResult(-5)
assert.Error(t, err)
assert.Equal(t, "negative", err.Error())
assert.Equal(t, -5, val)
})
}
// TestGetOrElse tests extracting value with default
func TestGetOrElse(t *testing.T) {
defaultValue := func(error) int { return 0 }
t.Run("GetOrElse on Right", func(t *testing.T) {
val := GetOrElse(defaultValue)(Right(42))
assert.Equal(t, 42, val)
})
t.Run("GetOrElse on Left", func(t *testing.T) {
val := GetOrElse(defaultValue)(Left[int](errors.New("error")))
assert.Equal(t, 0, val)
})
}

View File

@@ -0,0 +1,41 @@
// 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 result
type (
eitherFunctor[A, B any] struct{}
Functor[A, B any] interface {
Map(f func(A) B) Operator[A, B]
}
)
func (o eitherFunctor[A, B]) Map(f func(A) B) Operator[A, B] {
return Map(f)
}
// MakeFunctor creates a Functor instance for Result operations.
// A functor provides the Map operation that transforms values inside a context
// while preserving the structure.
//
// Example:
//
// f := result.MakeFunctor[int, string]()
// val, err := f.Map(strconv.Itoa)(result.Right[error](42))
// // val is "42", err is nil
func MakeFunctor[A, B any]() Functor[A, B] {
return eitherFunctor[A, B]{}
}

303
v2/idiomatic/result/gen.go Normal file
View File

@@ -0,0 +1,303 @@
// Code generated by go generate; DO NOT EDIT.
// This file was generated by robots at
// 2025-03-09 23:52:50.4396159 +0100 CET m=+0.001548701
//
// This file contains generated functions for converting between Either and tuple-based functions.
// It provides Eitherize/Uneitherize functions for functions with 0-15 parameters,
// as well as SequenceT/SequenceTuple/TraverseTuple functions for working with tuples of Either values.
// Code generated by go generate; DO NOT EDIT.
// This file was generated by robots at
// 2025-03-09 23:52:50.4396159 +0100 CET m=+0.001548701
package result
// TraverseTuple1 converts a [Tuple1] of [A] via transformation functions transforming [A] to [Either[A]] into a [Either[Tuple1]].
func TraverseTuple1[F1 ~func(A1) (T1, error), E, A1, T1 any](f1 F1) func(A1) (T1, error) {
return func(a1 A1) (t1 T1, err error) {
t1, err = f1(a1)
if err != nil {
return
}
return
}
}
// TraverseTuple2 converts a [Tuple2] of [A] via transformation functions transforming [A] to [Either[A]] into a [Either[Tuple2]].
func TraverseTuple2[F1 ~func(A1) (T1, error), F2 ~func(A2) (T2, error), E, A1, T1, A2, T2 any](f1 F1, f2 F2) func(A1, A2) (T1, T2, error) {
return func(a1 A1, a2 A2) (t1 T1, t2 T2, err error) {
t1, err = f1(a1)
if err != nil {
return
}
t2, err = f2(a2)
if err != nil {
return
}
return
}
}
// TraverseTuple3 converts a [Tuple3] of [A] via transformation functions transforming [A] to [Either[A]] into a [Either[Tuple3]].
func TraverseTuple3[F1 ~func(A1) (T1, error), F2 ~func(A2) (T2, error), F3 ~func(A3) (T3, error), E, A1, T1, A2, T2, A3, T3 any](f1 F1, f2 F2, f3 F3) func(A1, A2, A3) (T1, T2, T3, error) {
return func(a1 A1, a2 A2, a3 A3) (t1 T1, t2 T2, t3 T3, err error) {
t1, err = f1(a1)
if err != nil {
return
}
t2, err = f2(a2)
if err != nil {
return
}
t3, err = f3(a3)
if err != nil {
return
}
return
}
}
// TraverseTuple4 converts a [Tuple4] of [A] via transformation functions transforming [A] to [Either[A]] into a [Either[Tuple4]].
func TraverseTuple4[F1 ~func(A1) (T1, error), F2 ~func(A2) (T2, error), F3 ~func(A3) (T3, error), F4 ~func(A4) (T4, error), E, A1, T1, A2, T2, A3, T3, A4, T4 any](f1 F1, f2 F2, f3 F3, f4 F4) func(A1, A2, A3, A4) (T1, T2, T3, T4, error) {
return func(a1 A1, a2 A2, a3 A3, a4 A4) (t1 T1, t2 T2, t3 T3, t4 T4, err error) {
t1, err = f1(a1)
if err != nil {
return
}
t2, err = f2(a2)
if err != nil {
return
}
t3, err = f3(a3)
if err != nil {
return
}
t4, err = f4(a4)
if err != nil {
return
}
return
}
}
// TraverseTuple5 converts a [Tuple5] of [A] via transformation functions transforming [A] to [Either[A]] into a [Either[Tuple5]].
func TraverseTuple5[F1 ~func(A1) (T1, error), F2 ~func(A2) (T2, error), F3 ~func(A3) (T3, error), F4 ~func(A4) (T4, error), F5 ~func(A5) (T5, error), E, A1, T1, A2, T2, A3, T3, A4, T4, A5, T5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(A1, A2, A3, A4, A5) (T1, T2, T3, T4, T5, error) {
return func(a1 A1, a2 A2, a3 A3, a4 A4, a5 A5) (t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, err error) {
t1, err = f1(a1)
if err != nil {
return
}
t2, err = f2(a2)
if err != nil {
return
}
t3, err = f3(a3)
if err != nil {
return
}
t4, err = f4(a4)
if err != nil {
return
}
t5, err = f5(a5)
if err != nil {
return
}
return
}
}
// TraverseTuple6 converts a [Tuple6] of [A] via transformation functions transforming [A] to [Either[A]] into a [Either[Tuple6]].
func TraverseTuple6[F1 ~func(A1) (T1, error), F2 ~func(A2) (T2, error), F3 ~func(A3) (T3, error), F4 ~func(A4) (T4, error), F5 ~func(A5) (T5, error), F6 ~func(A6) (T6, error), E, A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func(A1, A2, A3, A4, A5, A6) (T1, T2, T3, T4, T5, T6, error) {
return func(a1 A1, a2 A2, a3 A3, a4 A4, a5 A5, a6 A6) (t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, err error) {
t1, err = f1(a1)
if err != nil {
return
}
t2, err = f2(a2)
if err != nil {
return
}
t3, err = f3(a3)
if err != nil {
return
}
t4, err = f4(a4)
if err != nil {
return
}
t5, err = f5(a5)
if err != nil {
return
}
t6, err = f6(a6)
if err != nil {
return
}
return
}
}
// TraverseTuple7 converts a [Tuple7] of [A] via transformation functions transforming [A] to [Either[A]] into a [Either[Tuple7]].
func TraverseTuple7[F1 ~func(A1) (T1, error), F2 ~func(A2) (T2, error), F3 ~func(A3) (T3, error), F4 ~func(A4) (T4, error), F5 ~func(A5) (T5, error), F6 ~func(A6) (T6, error), F7 ~func(A7) (T7, error), E, A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func(A1, A2, A3, A4, A5, A6, A7) (T1, T2, T3, T4, T5, T6, T7, error) {
return func(a1 A1, a2 A2, a3 A3, a4 A4, a5 A5, a6 A6, a7 A7) (t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, err error) {
t1, err = f1(a1)
if err != nil {
return
}
t2, err = f2(a2)
if err != nil {
return
}
t3, err = f3(a3)
if err != nil {
return
}
t4, err = f4(a4)
if err != nil {
return
}
t5, err = f5(a5)
if err != nil {
return
}
t6, err = f6(a6)
if err != nil {
return
}
t7, err = f7(a7)
if err != nil {
return
}
return
}
}
// TraverseTuple8 converts a [Tuple8] of [A] via transformation functions transforming [A] to [Either[A]] into a [Either[Tuple8]].
func TraverseTuple8[F1 ~func(A1) (T1, error), F2 ~func(A2) (T2, error), F3 ~func(A3) (T3, error), F4 ~func(A4) (T4, error), F5 ~func(A5) (T5, error), F6 ~func(A6) (T6, error), F7 ~func(A7) (T7, error), F8 ~func(A8) (T8, error), E, A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func(A1, A2, A3, A4, A5, A6, A7, A8) (T1, T2, T3, T4, T5, T6, T7, T8, error) {
return func(a1 A1, a2 A2, a3 A3, a4 A4, a5 A5, a6 A6, a7 A7, a8 A8) (t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, err error) {
t1, err = f1(a1)
if err != nil {
return
}
t2, err = f2(a2)
if err != nil {
return
}
t3, err = f3(a3)
if err != nil {
return
}
t4, err = f4(a4)
if err != nil {
return
}
t5, err = f5(a5)
if err != nil {
return
}
t6, err = f6(a6)
if err != nil {
return
}
t7, err = f7(a7)
if err != nil {
return
}
t8, err = f8(a8)
if err != nil {
return
}
return
}
}
// TraverseTuple9 converts a [Tuple9] of [A] via transformation functions transforming [A] to [Either[A]] into a [Either[Tuple9]].
func TraverseTuple9[F1 ~func(A1) (T1, error), F2 ~func(A2) (T2, error), F3 ~func(A3) (T3, error), F4 ~func(A4) (T4, error), F5 ~func(A5) (T5, error), F6 ~func(A6) (T6, error), F7 ~func(A7) (T7, error), F8 ~func(A8) (T8, error), F9 ~func(A9) (T9, error), E, A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8, A9, T9 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func(A1, A2, A3, A4, A5, A6, A7, A8, A9) (T1, T2, T3, T4, T5, T6, T7, T8, T9, error) {
return func(a1 A1, a2 A2, a3 A3, a4 A4, a5 A5, a6 A6, a7 A7, a8 A8, a9 A9) (t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, err error) {
t1, err = f1(a1)
if err != nil {
return
}
t2, err = f2(a2)
if err != nil {
return
}
t3, err = f3(a3)
if err != nil {
return
}
t4, err = f4(a4)
if err != nil {
return
}
t5, err = f5(a5)
if err != nil {
return
}
t6, err = f6(a6)
if err != nil {
return
}
t7, err = f7(a7)
if err != nil {
return
}
t8, err = f8(a8)
if err != nil {
return
}
t9, err = f9(a9)
if err != nil {
return
}
return
}
}
// TraverseTuple10 converts a [Tuple10] of [A] via transformation functions transforming [A] to [Either[A]] into a [Either[Tuple10]].
func TraverseTuple10[F1 ~func(A1) (T1, error), F2 ~func(A2) (T2, error), F3 ~func(A3) (T3, error), F4 ~func(A4) (T4, error), F5 ~func(A5) (T5, error), F6 ~func(A6) (T6, error), F7 ~func(A7) (T7, error), F8 ~func(A8) (T8, error), F9 ~func(A9) (T9, error), F10 ~func(A10) (T10, error), E, A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8, A9, T9, A10, T10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10) (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, error) {
return func(a1 A1, a2 A2, a3 A3, a4 A4, a5 A5, a6 A6, a7 A7, a8 A8, a9 A9, a10 A10) (t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, err error) {
t1, err = f1(a1)
if err != nil {
return
}
t2, err = f2(a2)
if err != nil {
return
}
t3, err = f3(a3)
if err != nil {
return
}
t4, err = f4(a4)
if err != nil {
return
}
t5, err = f5(a5)
if err != nil {
return
}
t6, err = f6(a6)
if err != nil {
return
}
t7, err = f7(a7)
if err != nil {
return
}
t8, err = f8(a8)
if err != nil {
return
}
t9, err = f9(a9)
if err != nil {
return
}
t10, err = f10(a10)
if err != nil {
return
}
return
}
}

View File

@@ -0,0 +1,202 @@
// Copyright (c) 2023 - 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 result
import (
"errors"
"testing"
)
// Test TraverseTuple1
func TestTraverseTuple1(t *testing.T) {
f := func(x int) (string, error) {
if x > 0 {
return Right("positive")
}
return Left[string](errors.New("negative"))
}
result, err := TraverseTuple1[func(int) (string, error), error](f)(5)
AssertEq(Right("positive"))(result, err)(t)
result, err = TraverseTuple1[func(int) (string, error), error](f)(-1)
AssertEq(Left[string](errors.New("negative")))(result, err)(t)
}
// Test TraverseTuple2
func TestTraverseTuple2(t *testing.T) {
f1 := func(x int) (int, error) {
return Right(x * 2)
}
f2 := func(x int) (int, error) {
return Right(x * 2)
}
r1, r2, err := TraverseTuple2[func(int) (int, error), func(int) (int, error), error](f1, f2)(1, 2)
AssertEq(Right(2))(r1, err)(t)
AssertEq(Right(4))(r2, err)(t)
}
// Test TraverseTuple3
func TestTraverseTuple3(t *testing.T) {
f1 := func(x int) (int, error) {
return Right(x * 2)
}
f2 := func(x int) (int, error) {
return Right(x * 2)
}
f3 := func(x int) (int, error) {
return Right(x * 2)
}
r1, r2, r3, err := TraverseTuple3[func(int) (int, error), func(int) (int, error), func(int) (int, error), error](f1, f2, f3)(1, 2, 3)
AssertEq(Right(2))(r1, err)(t)
AssertEq(Right(4))(r2, err)(t)
AssertEq(Right(6))(r3, err)(t)
}
// Test TraverseTuple4
func TestTraverseTuple4(t *testing.T) {
f1 := func(x int) (int, error) {
return Right(x * 2)
}
f2 := func(x int) (int, error) {
return Right(x * 2)
}
f3 := func(x int) (int, error) {
return Right(x * 2)
}
f4 := func(x int) (int, error) {
return Right(x * 2)
}
r1, r2, r3, r4, err := TraverseTuple4[func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), error](f1, f2, f3, f4)(1, 2, 3, 4)
AssertEq(Right(2))(r1, err)(t)
AssertEq(Right(4))(r2, err)(t)
AssertEq(Right(6))(r3, err)(t)
AssertEq(Right(8))(r4, err)(t)
}
// Test TraverseTuple5
func TestTraverseTuple5(t *testing.T) {
f1 := func(x int) (int, error) {
return Right(x * 2)
}
f2 := func(x int) (int, error) {
return Right(x * 2)
}
f3 := func(x int) (int, error) {
return Right(x * 2)
}
f4 := func(x int) (int, error) {
return Right(x * 2)
}
f5 := func(x int) (int, error) {
return Right(x * 2)
}
r1, r2, r3, r4, r5, err := TraverseTuple5[func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), error](f1, f2, f3, f4, f5)(1, 2, 3, 4, 5)
AssertEq(Right(2))(r1, err)(t)
AssertEq(Right(4))(r2, err)(t)
AssertEq(Right(6))(r3, err)(t)
AssertEq(Right(8))(r4, err)(t)
AssertEq(Right(10))(r5, err)(t)
}
// Test TraverseTuple6
func TestTraverseTuple6(t *testing.T) {
f := func(x int) (int, error) {
return Right(x * 2)
}
r1, r2, r3, r4, r5, r6, err := TraverseTuple6[func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), error](f, f, f, f, f, f)(1, 2, 3, 4, 5, 6)
AssertEq(Right(2))(r1, err)(t)
AssertEq(Right(4))(r2, err)(t)
AssertEq(Right(6))(r3, err)(t)
AssertEq(Right(8))(r4, err)(t)
AssertEq(Right(10))(r5, err)(t)
AssertEq(Right(12))(r6, err)(t)
}
// Test TraverseTuple7
func TestTraverseTuple7(t *testing.T) {
f := func(x int) (int, error) {
return Right(x * 2)
}
r1, r2, r3, r4, r5, r6, r7, err := TraverseTuple7[func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), error](f, f, f, f, f, f, f)(1, 2, 3, 4, 5, 6, 7)
AssertEq(Right(2))(r1, err)(t)
AssertEq(Right(4))(r2, err)(t)
AssertEq(Right(6))(r3, err)(t)
AssertEq(Right(8))(r4, err)(t)
AssertEq(Right(10))(r5, err)(t)
AssertEq(Right(12))(r6, err)(t)
AssertEq(Right(14))(r7, err)(t)
}
// Test TraverseTuple8
func TestTraverseTuple8(t *testing.T) {
f := func(x int) (int, error) {
return Right(x * 2)
}
r1, r2, r3, r4, r5, r6, r7, r8, err := TraverseTuple8[func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), error](f, f, f, f, f, f, f, f)(1, 2, 3, 4, 5, 6, 7, 8)
AssertEq(Right(2))(r1, err)(t)
AssertEq(Right(4))(r2, err)(t)
AssertEq(Right(6))(r3, err)(t)
AssertEq(Right(8))(r4, err)(t)
AssertEq(Right(10))(r5, err)(t)
AssertEq(Right(12))(r6, err)(t)
AssertEq(Right(14))(r7, err)(t)
AssertEq(Right(16))(r8, err)(t)
}
// Test TraverseTuple9
func TestTraverseTuple9(t *testing.T) {
f := func(x int) (int, error) {
return Right(x * 2)
}
r1, r2, r3, r4, r5, r6, r7, r8, r9, err := TraverseTuple9[func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), error](f, f, f, f, f, f, f, f, f)(1, 2, 3, 4, 5, 6, 7, 8, 9)
AssertEq(Right(2))(r1, err)(t)
AssertEq(Right(4))(r2, err)(t)
AssertEq(Right(6))(r3, err)(t)
AssertEq(Right(8))(r4, err)(t)
AssertEq(Right(10))(r5, err)(t)
AssertEq(Right(12))(r6, err)(t)
AssertEq(Right(14))(r7, err)(t)
AssertEq(Right(16))(r8, err)(t)
AssertEq(Right(18))(r9, err)(t)
}
// Test TraverseTuple10
func TestTraverseTuple10(t *testing.T) {
f := func(x int) (int, error) {
return Right(x * 2)
}
r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, err := TraverseTuple10[func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), func(int) (int, error), error](f, f, f, f, f, f, f, f, f, f)(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
AssertEq(Right(2))(r1, err)(t)
AssertEq(Right(4))(r2, err)(t)
AssertEq(Right(6))(r3, err)(t)
AssertEq(Right(8))(r4, err)(t)
AssertEq(Right(10))(r5, err)(t)
AssertEq(Right(12))(r6, err)(t)
AssertEq(Right(14))(r7, err)(t)
AssertEq(Right(16))(r8, err)(t)
AssertEq(Right(18))(r9, err)(t)
AssertEq(Right(20))(r10, err)(t)
}

View File

@@ -0,0 +1,70 @@
// Copyright (c) 2023 - 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 http provides utilities for creating HTTP requests with Either-based error handling.
package http
import (
"bytes"
"net/http"
)
var (
// PostRequest creates a POST HTTP request with a body.
// Usage: PostRequest(url)(body) returns Either[error, *http.Request]
//
// Example:
//
// request := http.PostRequest("https://api.example.com/data")([]byte(`{"key":"value"}`))
PostRequest = bodyRequest("POST")
// PutRequest creates a PUT HTTP request with a body.
// Usage: PutRequest(url)(body) returns Either[error, *http.Request]
PutRequest = bodyRequest("PUT")
// GetRequest creates a GET HTTP request without a body.
// Usage: GetRequest(url) returns Either[error, *http.Request]
//
// Example:
//
// request := http.GetRequest("https://api.example.com/data")
GetRequest = noBodyRequest("GET")
// DeleteRequest creates a DELETE HTTP request without a body.
// Usage: DeleteRequest(url) returns Either[error, *http.Request]
DeleteRequest = noBodyRequest("DELETE")
// OptionsRequest creates an OPTIONS HTTP request without a body.
// Usage: OptionsRequest(url) returns Either[error, *http.Request]
OptionsRequest = noBodyRequest("OPTIONS")
// HeadRequest creates a HEAD HTTP request without a body.
// Usage: HeadRequest(url) returns Either[error, *http.Request]
HeadRequest = noBodyRequest("HEAD")
)
func bodyRequest(method string) func(string) func([]byte) (*http.Request, error) {
return func(url string) func([]byte) (*http.Request, error) {
return func(body []byte) (*http.Request, error) {
return http.NewRequest(method, url, bytes.NewReader(body))
}
}
}
func noBodyRequest(method string) func(string) (*http.Request, error) {
return func(url string) (*http.Request, error) {
return http.NewRequest(method, url, nil)
}
}

View File

@@ -0,0 +1,56 @@
// Copyright (c) 2023 - 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 result
import (
"log"
L "github.com/IBM/fp-go/v2/logging"
)
func _log[A any](left func(string, ...any), right func(string, ...any), prefix string) Operator[A, A] {
return func(a A, err error) (A, error) {
if err != nil {
left("%s: %v", prefix, err)
} else {
right("%s: %v", prefix, a)
}
return a, err
}
}
// Logger creates a logging function for Either values that logs both Left and Right cases.
// The function logs the value and then returns the original Either unchanged.
//
// Parameters:
// - loggers: Optional log.Logger instances. If none provided, uses default logger.
//
// Example:
//
// logger := either.Logger[error, int]()
// result := F.Pipe2(
// either.Right[error](42),
// logger("Processing"),
// either.Map(N.Mul(2)),
// )
// // Logs: "Processing: 42"
// // result is Right(84)
func Logger[A any](loggers ...*log.Logger) func(string) Operator[A, A] {
left, right := L.LoggingCallbacks(loggers...)
return func(prefix string) Operator[A, A] {
return _log[A](left, right, prefix)
}
}

View File

@@ -0,0 +1,38 @@
// Copyright (c) 2023 - 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 result
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestLogger(t *testing.T) {
l := Logger[string]()
rVal, rErr := Right("test")
resVal, resErr := Pipe2(
"test",
Right[string],
l("out"),
)
assert.Equal(t, rVal, resVal)
assert.Equal(t, rErr, resErr)
}

View File

@@ -0,0 +1,59 @@
// 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 result
type (
eitherMonad[A, B any] struct{}
Monad[A, B any] interface {
Applicative[A, B]
Chainable[A, B]
}
)
func (o eitherMonad[A, B]) Of(a A) (A, error) {
return Of(a)
}
func (o eitherMonad[A, B]) Map(f func(A) B) Operator[A, B] {
return Map(f)
}
func (o eitherMonad[A, B]) Chain(f func(A) (B, error)) Operator[A, B] {
return Chain(f)
}
func (o eitherMonad[A, B]) Ap(a A, err error) Operator[func(A) B, B] {
return Ap[B](a, err)
}
// MakeMonad creates a Monad instance for Result operations.
// A monad combines the capabilities of Functor (Map), Applicative (Ap), and Chain (flatMap/bind).
// This allows for sequential composition of computations that may fail.
//
// Example:
//
// m := result.MakeMonad[int, string]()
// val, err := m.Chain(func(x int) (string, error) {
// if x > 0 {
// return result.Right[error](strconv.Itoa(x))
// }
// return result.Left[string](errors.New("negative"))
// })(result.Right[error](42))
// // val is "42", err is nil
func MakeMonad[A, B any]() Monad[A, B] {
return eitherMonad[A, B]{}
}

View File

@@ -0,0 +1,59 @@
// Copyright (c) 2023 - 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 result
import (
L "github.com/IBM/fp-go/v2/lazy"
M "github.com/IBM/fp-go/v2/monoid"
)
// AlternativeMonoid creates a monoid for Either using applicative semantics.
// The empty value is Right with the monoid's empty value.
// Combines values using applicative operations.
//
// Example:
//
// import "github.com/IBM/fp-go/v2/monoid"
// intAdd := monoid.MakeMonoid(0, func(a, b int) int { return a + b })
// m := either.AlternativeMonoid[error](intAdd)
// result := m.Concat(either.Right[error](1), either.Right[error](2))
// // result is Right(3)
func AlternativeMonoid[A any](m M.Monoid[A]) Monoid[A] {
return M.AlternativeMonoid(
Of[A],
MonadMap[A, func(A) A],
MonadAp[A, E, A],
MonadAlt[A],
m,
)
}
// AltMonoid creates a monoid for Either using the Alt operation.
// The empty value is provided as a lazy computation.
// When combining, returns the first Right value, or the second if the first is Left.
//
// Example:
//
// zero := func() either.Either[error, int] { return either.Left[int](errors.New("empty")) }
// m := either.AltMonoid[error, int](zero)
// result := m.Concat(either.Left[int](errors.New("err1")), either.Right[error](42))
// // result is Right(42)
func AltMonoid[A any](zero L.Lazy[Either[A]]) Monoid[A] {
return M.AltMonoid(
zero,
MonadAlt[A],
)
}

View File

@@ -0,0 +1,39 @@
// 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 result
type (
eitherPointed[A any] struct{}
Pointed[A any] interface {
Of(a A) (A, error)
}
)
func (o eitherPointed[A]) Of(a A) (A, error) {
return Of(a)
}
// Pointed implements the pointed functor operations for Either.
// A pointed functor provides the Of operation to lift a value into the Either context.
//
// Example:
//
// p := either.Pointed[error, int]()
// result := p.Of(42) // Right(42)
func MakePointed[A any]() Pointed[A] {
return eitherPointed[A]{}
}

View File

@@ -0,0 +1,114 @@
// Copyright (c) 2023 - 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 result
// TraverseRecordG transforms a map by applying a function that returns an Either to each value.
// If any value produces a Left, the entire result is that Left (short-circuits).
// Otherwise, returns Right containing the map of all Right values.
// The G suffix indicates support for generic map types.
//
// Example:
//
// parse := func(s string) either.Either[error, int] {
// v, err := strconv.Atoi(s)
// return either.FromError(v, err)
// }
// result := either.TraverseRecordG[map[string]string, map[string]int](parse)(map[string]string{"a": "1", "b": "2"})
// // result is Right(map[string]int{"a": 1, "b": 2})
//
//go:inline
func TraverseRecordG[GA ~map[K]A, GB ~map[K]B, K comparable, A, B any](f Kleisli[A, B]) Kleisli[GA, GB] {
return func(ga GA) (GB, error) {
bs := make(GB)
for k, a := range ga {
b, err := f(a)
if err != nil {
return Left[GB](err)
}
bs[k] = b
}
return Of(bs)
}
}
// TraverseRecord transforms a map by applying a function that returns an Either to each value.
// If any value produces a Left, the entire result is that Left (short-circuits).
// Otherwise, returns Right containing the map of all Right values.
//
// Example:
//
// parse := func(s string) either.Either[error, int] {
// v, err := strconv.Atoi(s)
// return either.FromError(v, err)
// }
// result := either.TraverseRecord[string](parse)(map[string]string{"a": "1", "b": "2"})
// // result is Right(map[string]int{"a": 1, "b": 2})
//
//go:inline
func TraverseRecord[K comparable, A, B any](f Kleisli[A, B]) Kleisli[map[K]A, map[K]B] {
return TraverseRecordG[map[K]A, map[K]B](f)
}
// TraverseRecordWithIndexG transforms a map by applying an indexed function that returns an Either.
// The function receives both the key and the value.
// If any value produces a Left, the entire result is that Left (short-circuits).
// The G suffix indicates support for generic map types.
//
// Example:
//
// validate := func(k string, v string) either.Either[error, string] {
// if len(v) > 0 {
// return either.Right[error](k + ":" + v)
// }
// return either.Left[string](fmt.Errorf("empty value for key %s", k))
// }
// result := either.TraverseRecordWithIndexG[map[string]string, map[string]string](validate)(map[string]string{"a": "1"})
// // result is Right(map[string]string{"a": "a:1"})
//
//go:inline
func TraverseRecordWithIndexG[GA ~map[K]A, GB ~map[K]B, K comparable, A, B any](f func(K, A) (B, error)) Kleisli[GA, GB] {
return func(ga GA) (GB, error) {
bs := make(GB)
for k, a := range ga {
b, err := f(k, a)
if err != nil {
return Left[GB](err)
}
bs[k] = b
}
return Of(bs)
}
}
// TraverseRecordWithIndex transforms a map by applying an indexed function that returns an Either.
// The function receives both the key and the value.
// If any value produces a Left, the entire result is that Left (short-circuits).
//
// Example:
//
// validate := func(k string, v string) either.Either[error, string] {
// if len(v) > 0 {
// return either.Right[error](k + ":" + v)
// }
// return either.Left[string](fmt.Errorf("empty value for key %s", k))
// }
// result := either.TraverseRecordWithIndex[string](validate)(map[string]string{"a": "1"})
// // result is Right(map[string]string{"a": "a:1"})
//
//go:inline
func TraverseRecordWithIndex[K comparable, A, B any](f func(K, A) (B, error)) Kleisli[map[K]A, map[K]B] {
return TraverseRecordWithIndexG[map[K]A, map[K]B](f)
}

View File

@@ -0,0 +1,253 @@
// Copyright (c) 2023 - 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 result
import (
"errors"
"fmt"
"strconv"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestTraverseRecordG_Success tests successful traversal of a map
func TestTraverseRecordG_Success(t *testing.T) {
parse := func(s string) (int, error) {
return strconv.Atoi(s)
}
input := map[string]string{"a": "1", "b": "2", "c": "3"}
result, err := TraverseRecordG[map[string]string, map[string]int](parse)(input)
require.NoError(t, err)
assert.Equal(t, 1, result["a"])
assert.Equal(t, 2, result["b"])
assert.Equal(t, 3, result["c"])
}
// TestTraverseRecordG_Error tests that traversal short-circuits on error
func TestTraverseRecordG_Error(t *testing.T) {
parse := func(s string) (int, error) {
return strconv.Atoi(s)
}
input := map[string]string{"a": "1", "b": "bad", "c": "3"}
result, err := TraverseRecordG[map[string]string, map[string]int](parse)(input)
require.Error(t, err)
assert.Nil(t, result)
}
// TestTraverseRecordG_EmptyMap tests traversal of an empty map
func TestTraverseRecordG_EmptyMap(t *testing.T) {
parse := func(s string) (int, error) {
return strconv.Atoi(s)
}
input := map[string]string{}
result, err := TraverseRecordG[map[string]string, map[string]int](parse)(input)
require.NoError(t, err)
assert.Empty(t, result)
assert.NotNil(t, result) // Should be an empty map, not nil
}
// TestTraverseRecordG_CustomMapType tests with custom map types
func TestTraverseRecordG_CustomMapType(t *testing.T) {
type StringMap map[string]string
type IntMap map[string]int
parse := func(s string) (int, error) {
return strconv.Atoi(s)
}
input := StringMap{"x": "10", "y": "20"}
result, err := TraverseRecordG[StringMap, IntMap](parse)(input)
require.NoError(t, err)
assert.Equal(t, IntMap{"x": 10, "y": 20}, result)
}
// TestTraverseRecord_Success tests successful traversal
func TestTraverseRecord_Success(t *testing.T) {
validate := func(s string) (int, error) {
n, err := strconv.Atoi(s)
if err != nil {
return 0, err
}
if n < 0 {
return 0, errors.New("negative number")
}
return n * 2, nil
}
input := map[string]string{"a": "1", "b": "2"}
result, err := TraverseRecord[string, string, int](validate)(input)
require.NoError(t, err)
assert.Equal(t, 2, result["a"])
assert.Equal(t, 4, result["b"])
}
// TestTraverseRecord_ValidationError tests validation failure
func TestTraverseRecord_ValidationError(t *testing.T) {
validate := func(s string) (int, error) {
n, err := strconv.Atoi(s)
if err != nil {
return 0, err
}
if n < 0 {
return 0, errors.New("negative number")
}
return n, nil
}
input := map[string]string{"a": "1", "b": "-5"}
result, err := TraverseRecord[string, string, int](validate)(input)
require.Error(t, err)
assert.Contains(t, err.Error(), "negative")
assert.Nil(t, result)
}
// TestTraverseRecordWithIndexG_Success tests successful indexed traversal
func TestTraverseRecordWithIndexG_Success(t *testing.T) {
annotate := func(k string, v string) (string, error) {
if len(v) == 0 {
return "", fmt.Errorf("empty value for key %s", k)
}
return fmt.Sprintf("%s=%s", k, v), nil
}
input := map[string]string{"a": "1", "b": "2"}
result, err := TraverseRecordWithIndexG[map[string]string, map[string]string](annotate)(input)
require.NoError(t, err)
assert.Equal(t, "a=1", result["a"])
assert.Equal(t, "b=2", result["b"])
}
// TestTraverseRecordWithIndexG_Error tests error handling with key
func TestTraverseRecordWithIndexG_Error(t *testing.T) {
annotate := func(k string, v string) (string, error) {
if len(v) == 0 {
return "", fmt.Errorf("empty value for key %s", k)
}
return v, nil
}
input := map[string]string{"a": "1", "b": ""}
result, err := TraverseRecordWithIndexG[map[string]string, map[string]string](annotate)(input)
require.Error(t, err)
assert.Contains(t, err.Error(), "key b")
assert.Nil(t, result)
}
// TestTraverseRecordWithIndexG_EmptyMap tests empty map
func TestTraverseRecordWithIndexG_EmptyMap(t *testing.T) {
annotate := func(k string, v string) (string, error) {
return k + ":" + v, nil
}
input := map[string]string{}
result, err := TraverseRecordWithIndexG[map[string]string, map[string]string](annotate)(input)
require.NoError(t, err)
assert.Empty(t, result)
assert.NotNil(t, result)
}
// TestTraverseRecordWithIndex_Success tests successful indexed traversal
func TestTraverseRecordWithIndex_Success(t *testing.T) {
check := func(k string, v int) (string, error) {
if v < 0 {
return "", fmt.Errorf("negative value for key %s", k)
}
return fmt.Sprintf("%s:%d", k, v*2), nil
}
input := map[string]int{"a": 1, "b": 2}
result, err := TraverseRecordWithIndex[string, int, string](check)(input)
require.NoError(t, err)
assert.Equal(t, "a:2", result["a"])
assert.Equal(t, "b:4", result["b"])
}
// TestTraverseRecordWithIndex_Error tests error with key info
func TestTraverseRecordWithIndex_Error(t *testing.T) {
check := func(k string, v int) (int, error) {
if v < 0 {
return 0, fmt.Errorf("negative value for key %s", k)
}
return v, nil
}
input := map[string]int{"ok": 1, "bad": -5}
result, err := TraverseRecordWithIndex[string, int, int](check)(input)
require.Error(t, err)
assert.Contains(t, err.Error(), "key bad")
assert.Nil(t, result)
}
// TestTraverseRecordWithIndex_TypeTransformation tests transforming types with key
func TestTraverseRecordWithIndex_TypeTransformation(t *testing.T) {
prefixKey := func(k string, v string) (string, error) {
return k + "_" + v, nil
}
input := map[string]string{"prefix": "value", "another": "test"}
result, err := TraverseRecordWithIndex[string, string, string](prefixKey)(input)
require.NoError(t, err)
assert.Equal(t, "prefix_value", result["prefix"])
assert.Equal(t, "another_test", result["another"])
}
// TestTraverseRecord_IntKeys tests with integer keys
func TestTraverseRecord_IntKeys(t *testing.T) {
double := func(n int) (int, error) {
return n * 2, nil
}
input := map[int]int{1: 10, 2: 20, 3: 30}
result, err := TraverseRecord[int, int, int](double)(input)
require.NoError(t, err)
assert.Equal(t, 20, result[1])
assert.Equal(t, 40, result[2])
assert.Equal(t, 60, result[3])
}
// TestTraverseRecordG_PreservesKeys tests that keys are preserved
func TestTraverseRecordG_PreservesKeys(t *testing.T) {
identity := func(s string) (string, error) {
return s, nil
}
input := map[string]string{"key1": "val1", "key2": "val2"}
result, err := TraverseRecordG[map[string]string, map[string]string](identity)(input)
require.NoError(t, err)
assert.Contains(t, result, "key1")
assert.Contains(t, result, "key2")
assert.Equal(t, "val1", result["key1"])
assert.Equal(t, "val2", result["key2"])
}

View File

@@ -0,0 +1,60 @@
// Copyright (c) 2023 - 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 result
// WithResource constructs a function that creates a resource, operates on it, and then releases it.
// This ensures proper resource cleanup even if operations fail.
// The resource is released immediately after the operation completes.
//
// Parameters:
// - onCreate: Function to create/acquire the resource
// - onRelease: Function to release/cleanup the resource
//
// Returns a function that takes an operation to perform on the resource.
//
// Example:
//
// withFile := either.WithResource(
// func() either.Either[error, *os.File] {
// return either.TryCatchError(os.Open("file.txt"))
// },
// func(f *os.File) either.Either[error, any] {
// return either.TryCatchError(f.Close())
// },
// )
// result := withFile(func(f *os.File) either.Either[error, string] {
// // Use file here
// return either.Right[error]("data")
// })
func WithResource[R, A, ANY any](onCreate func() (R, error), onRelease Kleisli[R, ANY]) Kleisli[Kleisli[R, A], A] {
return func(f func(R) (A, error)) (A, error) {
r, rerr := onCreate()
if rerr != nil {
return Left[A](rerr)
}
a, aerr := f(r)
_, nerr := onRelease(r)
if aerr != nil {
return Left[A](aerr)
}
if nerr != nil {
return Left[A](nerr)
}
return Of(a)
}
}

View File

@@ -0,0 +1,45 @@
// Copyright (c) 2023 - 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 result
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestWithResource(t *testing.T) {
onCreate := func() (*os.File, error) {
return os.CreateTemp("", "*")
}
onDelete := func(f *os.File) (any, error) {
return Chain(func(name string) (any, error) {
return any(name), os.Remove(name)
})(f.Name(), f.Close())
}
onHandler := func(f *os.File) (string, error) {
return Of(f.Name())
}
tempFile := WithResource[*os.File, string](onCreate, onDelete)
res, err := tempFile(onHandler)
assert.True(t, IsRight(res, err))
}

View File

@@ -0,0 +1,38 @@
// Copyright (c) 2023 - 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 result
import (
S "github.com/IBM/fp-go/v2/semigroup"
)
// AltSemigroup creates a semigroup for Either that uses the Alt operation for combining values.
// When combining two Either values, it returns the first Right value, or the second value if the first is Left.
//
// Example:
//
// sg := either.AltSemigroup[error, int]()
// result := sg.Concat(either.Left[int](errors.New("error")), either.Right[error](42))
// // result is Right(42)
// result2 := sg.Concat(either.Right[error](1), either.Right[error](2))
// // result2 is Right(1) - first Right wins
//
//go:inline
func AltSemigroup[A any]() S.Semigroup[Either[A]] {
return S.AltSemigroup(
MonadAlt[A],
)
}

View File

@@ -0,0 +1,103 @@
// Copyright (c) 2023 - 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 testing provides utilities for testing Either monad laws.
// This is useful for verifying that custom Either implementations satisfy the monad laws.
package testing
import (
"testing"
ET "github.com/IBM/fp-go/v2/either"
EQ "github.com/IBM/fp-go/v2/eq"
L "github.com/IBM/fp-go/v2/internal/monad/testing"
)
// AssertLaws asserts that the Either monad satisfies the monad laws.
// This includes testing:
// - Identity laws (left and right identity)
// - Associativity law
// - Functor laws
// - Applicative laws
//
// Parameters:
// - t: Testing context
// - eqe, eqa, eqb, eqc: Equality predicates for the types
// - ab: Function from A to B for testing
// - bc: Function from B to C for testing
//
// Returns a function that takes a value of type A and returns true if all laws hold.
//
// Example:
//
// func TestEitherLaws(t *testing.T) {
// eqInt := eq.FromStrictEquals[int]()
// eqString := eq.FromStrictEquals[string]()
// eqError := eq.FromStrictEquals[error]()
//
// ab := func(x int) string { return strconv.Itoa(x) }
// bc := func(s string) bool { return len(s) > 0 }
//
// testing.AssertLaws(t, eqError, eqInt, eqString, eq.FromStrictEquals[bool](), ab, bc)(42)
// }
func AssertLaws[A, B, C any](t *testing.T,
eqe EQ.Eq[E],
eqa EQ.Eq[A],
eqb EQ.Eq[B],
eqc EQ.Eq[C],
ab func(A) B,
bc func(B) C,
) func(a A) bool {
return L.AssertLaws(t,
ET.Eq(eqe, eqa),
ET.Eq(eqe, eqb),
ET.Eq(eqe, eqc),
ET.Of[A],
ET.Of[B],
ET.Of[C],
ET.Of[func(A) A],
ET.Of[func(A) B],
ET.Of[func(B) C],
ET.Of[func(func(A) B) B],
ET.MonadMap[A, A],
ET.MonadMap[A, B],
ET.MonadMap[A, C],
ET.MonadMap[B, C],
ET.MonadMap[func(B) C, func(func(A) B) func(A) C],
ET.MonadChain[A, A],
ET.MonadChain[A, B],
ET.MonadChain[A, C],
ET.MonadChain[B, C],
ET.MonadAp[A, E, A],
ET.MonadAp[B, E, A],
ET.MonadAp[C, E, B],
ET.MonadAp[C, E, A],
ET.MonadAp[B, E, func(A) B],
ET.MonadAp[func(A) C, E, func(A) B],
ab,
bc,
)
}

View File

@@ -0,0 +1,48 @@
// Copyright (c) 2023 - 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 testing
import (
"fmt"
"testing"
EQ "github.com/IBM/fp-go/v2/eq"
"github.com/stretchr/testify/assert"
)
func TestMonadLaws(t *testing.T) {
// some comparison
eqe := EQ.FromStrictEquals[string]()
eqa := EQ.FromStrictEquals[bool]()
eqb := EQ.FromStrictEquals[int]()
eqc := EQ.FromStrictEquals[string]()
ab := func(a bool) int {
if a {
return 1
}
return 0
}
bc := func(b int) string {
return fmt.Sprintf("value %d", b)
}
laws := AssertLaws(t, eqe, eqa, eqb, eqc, ab, bc)
assert.True(t, laws(true))
assert.True(t, laws(false))
}

View File

@@ -0,0 +1,72 @@
// Copyright (c) 2023 - 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 result
// Traverse converts an Either of some higher kinded type into the higher kinded type of an Either.
// This is a generic traversal operation that works with any applicative functor.
//
// Parameters:
// - mof: Lifts an Either into the target higher-kinded type
// - mmap: Maps over the target higher-kinded type
//
// Example (conceptual - requires understanding of higher-kinded types):
//
// // Traverse an Either[error, Option[int]] to Option[Either[error, int]]
// result := either.Traverse[int, error, int, option.Option[int], option.Option[either.Either[error, int]]](
// option.Of[either.Either[error, int]],
// option.Map[int, either.Either[error, int]],
// )(f)(eitherOfOption)
func Traverse[A, B, HKTB, HKTRB any](
mof func(B, error) HKTRB,
mmap func(Kleisli[B, B]) func(HKTB) HKTRB,
) func(func(A) HKTB) func(A, error) HKTRB {
return func(f func(A) HKTB) func(A, error) HKTRB {
right := mmap(Right[B])
return func(a A, err error) HKTRB {
if err != nil {
return mof(Left[B](err))
}
return right(f(a))
}
}
}
// Sequence converts an Either of some higher kinded type into the higher kinded type of an Either.
// This is the identity version of Traverse - it doesn't transform the values, just swaps the type constructors.
//
// Parameters:
// - mof: Lifts an Either into the target higher-kinded type
// - mmap: Maps over the target higher-kinded type
//
// Example (conceptual - requires understanding of higher-kinded types):
//
// // Sequence an Either[error, Option[int]] to Option[Either[error, int]]
// result := either.Sequence[error, int, option.Option[int], option.Option[either.Either[error, int]]](
// option.Of[either.Either[error, int]],
// option.Map[int, either.Either[error, int]],
// )(eitherOfOption)
func Sequence[A, HKTA, HKTRA any](
mof func(A, error) HKTRA,
mmap func(Kleisli[A, A]) func(HKTA) HKTRA,
) func(hkta HKTA, err error) HKTRA {
right := mmap(Right[A])
return func(hkta HKTA, err error) HKTRA {
if err != nil {
return mof(Left[A](err))
}
return right(hkta)
}
}

View File

@@ -0,0 +1,33 @@
// 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 result
import (
"github.com/IBM/fp-go/v2/endomorphism"
"github.com/IBM/fp-go/v2/optics/lens"
"github.com/IBM/fp-go/v2/option"
)
// Option is a type alias for option.Option, provided for convenience
// when working with Either and Option together.
type (
Option[A any] = option.Option[A]
Lens[S, T any] = lens.Lens[S, T]
Endomorphism[T any] = endomorphism.Endomorphism[T]
Kleisli[A, B any] = func(A) (B, error)
Operator[A, B any] = func(A, error) (B, error)
)

View File

@@ -0,0 +1,103 @@
// 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 result
import (
S "github.com/IBM/fp-go/v2/semigroup"
)
// ApV applies a function wrapped in a Result to a value wrapped in a Result,
// accumulating errors using a semigroup instead of short-circuiting.
//
// This function is designed for validation scenarios where you want to collect
// all validation errors rather than stopping at the first error. It differs
// from the standard [Ap] function in that it combines errors from both the
// function and the value using the provided semigroup operation.
//
// The function works as follows:
// - If both the value and the function have errors, it combines them using the semigroup
// - If only one has an error, it returns that error
// - If neither has an error, it applies the function to the value
//
// Type Parameters:
// - B: The result type after applying the function
// - A: The input type to the function
//
// Parameters:
// - sg: A semigroup that defines how to combine two error values. The semigroup's
// Concat operation determines how errors are accumulated (e.g., concatenating
// error messages, merging error lists, etc.)
//
// Returns:
// - A curried function that takes a value (A, error), then takes a function
// (func(A) B, error), and returns the result (B, error) with accumulated errors
//
// Behavior:
// - Right + Right: Applies the function to the value and returns Right(result)
// - Right + Left: Returns the Left error from the function
// - Left + Right: Returns the Left error from the value
// - Left + Left: Returns Left(sg.Concat(function_error, value_error))
//
// Example:
//
// import (
// "errors"
// "fmt"
// "strings"
// S "github.com/IBM/fp-go/v2/semigroup"
// "github.com/IBM/fp-go/v2/idiomatic/result"
// )
//
// // Create a semigroup that combines errors by concatenating their messages
// errorSemigroup := S.MakeSemigroup(func(e1, e2 error) error {
// return fmt.Errorf("%v; %v", e1, e2)
// })
//
// // ApV with both function and value having errors
// double := func(x int) int { return x * 2 }
// apv := result.ApV[int, int](errorSemigroup)
//
// value := result.Left[int](errors.New("invalid value"))
// fn := result.Left[func(int) int](errors.New("invalid function"))
//
// result := apv(value)(fn)
// // Left(error: "invalid function; invalid value")
//
// // ApV with successful application
// goodValue, _ := result.Right(5)
// goodFn, _ := result.Right(double)
// result2 := apv(goodValue)(goodFn)
// // Right(10)
func ApV[B, A any](sg S.Semigroup[error]) func(A, error) Operator[func(A) B, B] {
return func(a A, aerr error) Operator[func(A) B, B] {
return func(fab func(A) B, faberr error) (B, error) {
// Both have errors: combine them using the semigroup
if aerr != nil {
if faberr != nil {
return Left[B](sg.Concat(faberr, aerr))
}
// Only value has error
return Left[B](aerr)
}
// Only function has error
if faberr != nil {
return Left[B](faberr)
}
// Both are successful: apply function to value
return Of(fab(a))
}
}
}

View File

@@ -0,0 +1,373 @@
// 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 result
import (
"errors"
"fmt"
"strings"
"testing"
S "github.com/IBM/fp-go/v2/semigroup"
"github.com/stretchr/testify/assert"
)
// Helper function to create a semigroup that concatenates error messages
func makeErrorConcatSemigroup() S.Semigroup[error] {
return S.MakeSemigroup(func(e1, e2 error) error {
return fmt.Errorf("%v; %v", e1, e2)
})
}
// Helper function to create a semigroup that collects error messages in a slice
func makeErrorListSemigroup() S.Semigroup[error] {
return S.MakeSemigroup(func(e1, e2 error) error {
msg1 := e1.Error()
msg2 := e2.Error()
// Parse existing lists
var msgs []string
if strings.HasPrefix(msg1, "[") && strings.HasSuffix(msg1, "]") {
trimmed := strings.Trim(msg1, "[]")
if trimmed != "" {
msgs = strings.Split(trimmed, ", ")
}
} else {
msgs = []string{msg1}
}
if strings.HasPrefix(msg2, "[") && strings.HasSuffix(msg2, "]") {
trimmed := strings.Trim(msg2, "[]")
if trimmed != "" {
msgs = append(msgs, strings.Split(trimmed, ", ")...)
}
} else {
msgs = append(msgs, msg2)
}
return fmt.Errorf("[%s]", strings.Join(msgs, ", "))
})
}
// TestApV_BothRight tests ApV when both the value and function are Right
func TestApV_BothRight(t *testing.T) {
sg := makeErrorConcatSemigroup()
apv := ApV[int, int](sg)
double := func(x int) int { return x * 2 }
value, verr := Right(5)
fn, ferr := Right(double)
result, err := apv(value, verr)(fn, ferr)
assert.NoError(t, err)
assert.Equal(t, 10, result)
}
// TestApV_ValueLeft_FunctionRight tests ApV when value is Left and function is Right
func TestApV_ValueLeft_FunctionRight(t *testing.T) {
sg := makeErrorConcatSemigroup()
apv := ApV[int, int](sg)
double := func(x int) int { return x * 2 }
valueError := errors.New("invalid value")
value, verr := Left[int](valueError)
fn, ferr := Right(double)
result, err := apv(value, verr)(fn, ferr)
assert.Error(t, err)
assert.Equal(t, valueError, err)
assert.Equal(t, 0, result) // zero value for int
}
// TestApV_ValueRight_FunctionLeft tests ApV when value is Right and function is Left
func TestApV_ValueRight_FunctionLeft(t *testing.T) {
sg := makeErrorConcatSemigroup()
apv := ApV[int, int](sg)
fnError := errors.New("invalid function")
value, verr := Right(5)
fn, ferr := Left[func(int) int](fnError)
result, err := apv(value, verr)(fn, ferr)
assert.Error(t, err)
assert.Equal(t, fnError, err)
assert.Equal(t, 0, result) // zero value for int
}
// TestApV_BothLeft tests ApV when both value and function are Left
func TestApV_BothLeft(t *testing.T) {
sg := makeErrorConcatSemigroup()
apv := ApV[int, int](sg)
valueError := errors.New("invalid value")
fnError := errors.New("invalid function")
value, verr := Left[int](valueError)
fn, ferr := Left[func(int) int](fnError)
result, err := apv(value, verr)(fn, ferr)
assert.Error(t, err)
assert.Equal(t, 0, result) // zero value for int
// Verify the error message contains both errors
expectedMsg := "invalid function; invalid value"
assert.Equal(t, expectedMsg, err.Error())
}
// TestApV_BothLeft_WithListSemigroup tests error accumulation with a list semigroup
func TestApV_BothLeft_WithListSemigroup(t *testing.T) {
sg := makeErrorListSemigroup()
apv := ApV[string, string](sg)
valueError := errors.New("error1")
fnError := errors.New("error2")
value, verr := Left[string](valueError)
fn, ferr := Left[func(string) string](fnError)
result, err := apv(value, verr)(fn, ferr)
assert.Error(t, err)
assert.Equal(t, "", result) // zero value for string
// Verify both errors are in the list
expectedMsg := "[error2, error1]"
assert.Equal(t, expectedMsg, err.Error())
}
// TestApV_StringTransformation tests ApV with string transformation
func TestApV_StringTransformation(t *testing.T) {
sg := makeErrorConcatSemigroup()
apv := ApV[string, string](sg)
toUpper := func(s string) string { return strings.ToUpper(s) }
value, verr := Right("hello")
fn, ferr := Right(toUpper)
result, err := apv(value, verr)(fn, ferr)
assert.NoError(t, err)
assert.Equal(t, "HELLO", result)
}
// TestApV_DifferentTypes tests ApV with different input and output types
func TestApV_DifferentTypes(t *testing.T) {
sg := makeErrorConcatSemigroup()
apv := ApV[string, int](sg)
intToString := func(x int) string { return fmt.Sprintf("Number: %d", x) }
value, verr := Right(42)
fn, ferr := Right(intToString)
result, err := apv(value, verr)(fn, ferr)
assert.NoError(t, err)
assert.Equal(t, "Number: 42", result)
}
// TestApV_ComplexType tests ApV with complex types (structs)
func TestApV_ComplexType(t *testing.T) {
type Person struct {
Name string
Age int
}
sg := makeErrorConcatSemigroup()
apv := ApV[string, Person](sg)
getName := func(p Person) string { return p.Name }
person := Person{Name: "Alice", Age: 30}
value, verr := Right(person)
fn, ferr := Right(getName)
result, err := apv(value, verr)(fn, ferr)
assert.NoError(t, err)
assert.Equal(t, "Alice", result)
}
// TestApV_MultipleValidations demonstrates chaining multiple validations
func TestApV_MultipleValidations(t *testing.T) {
_ = makeErrorListSemigroup() // Semigroup available for future use
// Validation functions
validatePositive := func(x int) (int, error) {
if x > 0 {
return Right(x)
}
return Left[int](errors.New("must be positive"))
}
validateEven := func(x int) (int, error) {
if x%2 == 0 {
return Right(x)
}
return Left[int](errors.New("must be even"))
}
// Test valid value (positive and even)
t.Run("valid value", func(t *testing.T) {
value, err := Right(4)
validatedPositive, err1 := validatePositive(value)
validatedEven, err2 := validateEven(validatedPositive)
assert.NoError(t, err)
assert.NoError(t, err1)
assert.NoError(t, err2)
assert.Equal(t, 4, validatedEven)
})
// Test invalid value (negative)
t.Run("negative value", func(t *testing.T) {
value, _ := Right(-3)
_, err := validatePositive(value)
assert.Error(t, err)
assert.Equal(t, "must be positive", err.Error())
})
// Test invalid value (odd)
t.Run("odd value", func(t *testing.T) {
value, _ := Right(3)
validatedPositive, err1 := validatePositive(value)
_, err2 := validateEven(validatedPositive)
assert.NoError(t, err1)
assert.Error(t, err2)
assert.Equal(t, "must be even", err2.Error())
})
}
// TestApV_ZeroValues tests ApV with zero values
func TestApV_ZeroValues(t *testing.T) {
sg := makeErrorConcatSemigroup()
apv := ApV[int, int](sg)
identity := func(x int) int { return x }
value, verr := Right(0)
fn, ferr := Right(identity)
result, err := apv(value, verr)(fn, ferr)
assert.NoError(t, err)
assert.Equal(t, 0, result)
}
// TestApV_NilError tests that nil errors are handled correctly
func TestApV_NilError(t *testing.T) {
sg := makeErrorConcatSemigroup()
apv := ApV[string, string](sg)
identity := func(s string) string { return s }
// Right is equivalent to (value, nil)
value, verr := Right("test")
fn, ferr := Right(identity)
assert.Nil(t, verr)
assert.Nil(t, ferr)
result, err := apv(value, verr)(fn, ferr)
assert.NoError(t, err)
assert.Equal(t, "test", result)
}
// TestApV_SemigroupAssociativity tests that error combination is associative
func TestApV_SemigroupAssociativity(t *testing.T) {
sg := makeErrorConcatSemigroup()
e1 := errors.New("error1")
e2 := errors.New("error2")
e3 := errors.New("error3")
// (e1 + e2) + e3
left := sg.Concat(sg.Concat(e1, e2), e3)
// e1 + (e2 + e3)
right := sg.Concat(e1, sg.Concat(e2, e3))
assert.Equal(t, left.Error(), right.Error())
}
// TestApV_CustomSemigroup tests ApV with a custom semigroup
func TestApV_CustomSemigroup(t *testing.T) {
// Custom semigroup that counts errors
type ErrorCount struct {
count int
msg string
}
countSemigroup := S.MakeSemigroup(func(e1, e2 error) error {
// Simple counter in error message
return fmt.Errorf("combined: %v | %v", e1, e2)
})
apv := ApV[int, int](countSemigroup)
e1 := errors.New("first")
e2 := errors.New("second")
value, verr := Left[int](e1)
fn, ferr := Left[func(int) int](e2)
_, err := apv(value, verr)(fn, ferr)
assert.Error(t, err)
assert.Contains(t, err.Error(), "first")
assert.Contains(t, err.Error(), "second")
}
// BenchmarkApV_BothRight benchmarks the happy path
func BenchmarkApV_BothRight(b *testing.B) {
sg := makeErrorConcatSemigroup()
apv := ApV[int, int](sg)
double := func(x int) int { return x * 2 }
value, verr := Right(5)
fn, ferr := Right(double)
b.ResetTimer()
for i := 0; i < b.N; i++ {
apv(value, verr)(fn, ferr)
}
}
// BenchmarkApV_BothLeft benchmarks the error accumulation path
func BenchmarkApV_BothLeft(b *testing.B) {
sg := makeErrorConcatSemigroup()
apv := ApV[int, int](sg)
valueError := errors.New("value error")
fnError := errors.New("function error")
value, verr := Left[int](valueError)
fn, ferr := Left[func(int) int](fnError)
b.ResetTimer()
for i := 0; i < b.N; i++ {
apv(value, verr)(fn, ferr)
}
}

View File

@@ -0,0 +1,96 @@
// Copyright (c) 2023 - 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 result
// Variadic0 converts a function taking a slice and returning (R, error) into a variadic function returning Either.
//
// Example:
//
// sum := func(nums []int) (int, error) {
// total := 0
// for _, n := range nums { total += n }
// return total, nil
// }
// variadicSum := either.Variadic0(sum)
// result := variadicSum(1, 2, 3) // Right(6)
func Variadic0[V, R any](f func([]V) (R, error)) func(...V) Either[error, R] {
return func(v ...V) Either[error, R] {
return TryCatchError(f(v))
}
}
// Variadic1 converts a function with 1 fixed parameter and a slice into a variadic function returning Either.
func Variadic1[T1, V, R any](f func(T1, []V) (R, error)) func(T1, ...V) Either[error, R] {
return func(t1 T1, v ...V) Either[error, R] {
return TryCatchError(f(t1, v))
}
}
// Variadic2 converts a function with 2 fixed parameters and a slice into a variadic function returning Either.
func Variadic2[T1, T2, V, R any](f func(T1, T2, []V) (R, error)) func(T1, T2, ...V) Either[error, R] {
return func(t1 T1, t2 T2, v ...V) Either[error, R] {
return TryCatchError(f(t1, t2, v))
}
}
// Variadic3 converts a function with 3 fixed parameters and a slice into a variadic function returning Either.
func Variadic3[T1, T2, T3, V, R any](f func(T1, T2, T3, []V) (R, error)) func(T1, T2, T3, ...V) Either[error, R] {
return func(t1 T1, t2 T2, t3 T3, v ...V) Either[error, R] {
return TryCatchError(f(t1, t2, t3, v))
}
}
// Variadic4 converts a function with 4 fixed parameters and a slice into a variadic function returning Either.
func Variadic4[T1, T2, T3, T4, V, R any](f func(T1, T2, T3, T4, []V) (R, error)) func(T1, T2, T3, T4, ...V) Either[error, R] {
return func(t1 T1, t2 T2, t3 T3, t4 T4, v ...V) Either[error, R] {
return TryCatchError(f(t1, t2, t3, t4, v))
}
}
// Unvariadic0 converts a variadic function returning (R, error) into a function taking a slice and returning Either.
func Unvariadic0[V, R any](f func(...V) (R, error)) func([]V) Either[error, R] {
return func(v []V) Either[error, R] {
return TryCatchError(f(v...))
}
}
// Unvariadic1 converts a variadic function with 1 fixed parameter into a function taking a slice and returning Either.
func Unvariadic1[T1, V, R any](f func(T1, ...V) (R, error)) func(T1, []V) Either[error, R] {
return func(t1 T1, v []V) Either[error, R] {
return TryCatchError(f(t1, v...))
}
}
// Unvariadic2 converts a variadic function with 2 fixed parameters into a function taking a slice and returning Either.
func Unvariadic2[T1, T2, V, R any](f func(T1, T2, ...V) (R, error)) func(T1, T2, []V) Either[error, R] {
return func(t1 T1, t2 T2, v []V) Either[error, R] {
return TryCatchError(f(t1, t2, v...))
}
}
// Unvariadic3 converts a variadic function with 3 fixed parameters into a function taking a slice and returning Either.
func Unvariadic3[T1, T2, T3, V, R any](f func(T1, T2, T3, ...V) (R, error)) func(T1, T2, T3, []V) Either[error, R] {
return func(t1 T1, t2 T2, t3 T3, v []V) Either[error, R] {
return TryCatchError(f(t1, t2, t3, v...))
}
}
// Unvariadic4 converts a variadic function with 4 fixed parameters into a function taking a slice and returning Either.
func Unvariadic4[T1, T2, T3, T4, V, R any](f func(T1, T2, T3, T4, ...V) (R, error)) func(T1, T2, T3, T4, []V) Either[error, R] {
return func(t1 T1, t2 T2, t3 T3, t4 T4, v []V) Either[error, R] {
return TryCatchError(f(t1, t2, t3, t4, v...))
}
}