Skip to content

Commit

Permalink
Merge pull request #42 from taman9333/fold-result
Browse files Browse the repository at this point in the history
Add Fold function to Result monad
  • Loading branch information
samber authored Jun 22, 2024
2 parents 88450dd + 38e5f4f commit ef47e28
Show file tree
Hide file tree
Showing 9 changed files with 206 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ jobs:
stable: false
- uses: actions/checkout@v2
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v5
with:
args: --timeout 120s --max-same-issues 50
15 changes: 15 additions & 0 deletions either.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,18 @@ func (e Either[L, R]) MapRight(mapper func(R) Either[L, R]) Either[L, R] {

panic(eitherShouldBeLeftOrRight)
}

// leftValue returns left value of a Either struct.(implementation of Foldable interface)
func (e Either[L, R]) leftValue() L {
return e.left
}

// rightValue returns right value of a Either struct.(implementation of Foldable interface)
func (e Either[L, R]) rightValue() R {
return e.right
}

// hasLeft returns true if the Result represents an error state.
func (e Either[L, R]) hasLeftValue() bool {
return e.isLeft
}
40 changes: 40 additions & 0 deletions either_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package mo

import (
"errors"
"fmt"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -211,3 +213,41 @@ func TestEitherMapRight(t *testing.T) {
is.Equal(Either[int, string]{left: 42, right: "", isLeft: true}, e1)
is.Equal(Either[int, string]{left: 0, right: "plop", isLeft: false}, e2)
}

// TestEitherFoldSuccess tests the Fold method with a successful result.
func TestEitherFoldSuccess(t *testing.T) {
is := assert.New(t)
either := Either[error, int]{left: nil, right: 10, isLeft: false}

successFunc := func(value int) string {
return fmt.Sprintf("Success: %v", value)
}
failureFunc := func(err error) string {
return fmt.Sprintf("Failure: %v", err)
}

folded := Fold[error, int, string](either, successFunc, failureFunc)
expected := "Success: 10"

is.Equal(expected, folded)
}

// TestEitherFoldFailure tests the Fold method with a failure result.
func TestEitherFoldFailure(t *testing.T) {
err := errors.New("result error")
is := assert.New(t)

either := Either[error, int]{left: err, right: 0, isLeft: true}

successFunc := func(value int) string {
return fmt.Sprintf("Success: %v", value)
}
failureFunc := func(err error) string {
return fmt.Sprintf("Failure: %v", err)
}

folded := Fold[error, int, string](either, successFunc, failureFunc)
expected := fmt.Sprintf("Failure: %v", err)

is.Equal(expected, folded)
}
28 changes: 28 additions & 0 deletions fold.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package mo

// Foldable represents a type that can be folded into a single value
// based on its state.
//
// - T: the type of the value in the failure state (e.g., an error type).
// - U: the type of the value in the success state.
type Foldable[T any, U any] interface {
leftValue() T
rightValue() U
hasLeftValue() bool
}

// Fold applies one of the two functions based on the state of the Foldable type,
// and it returns the result of applying either successFunc or failureFunc.
//
// - T: the type of the failure value (e.g., an error type)
// - U: the type of the success value
// - R: the type of the return value from the folding functions
//
// successFunc is applied when the Foldable is in the success state (i.e., isLeft() is false).
// failureFunc is applied when the Foldable is in the failure state (i.e., isLeft() is true).
func Fold[T, U, R any](f Foldable[T, U], successFunc func(U) R, failureFunc func(T) R) R {
if f.hasLeftValue() {
return failureFunc(f.leftValue())
}
return successFunc(f.rightValue())
}
22 changes: 22 additions & 0 deletions option.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,25 @@ func (o Option[T]) Value() (driver.Value, error) {

return o.value, nil
}

// leftValue returns an error if the Option is None, otherwise nil
func (o Option[T]) leftValue() error {
if !o.isPresent {
return optionNoSuchElement
}
return nil
}

// rightValue returns the value if the Option is Some, otherwise the zero value of T
func (o Option[T]) rightValue() T {
if !o.isPresent {
var zero T
return zero
}
return o.value
}

// hasLeftValue returns true if the Option represents a None state
func (o Option[T]) hasLeftValue() bool {
return !o.isPresent
}
36 changes: 36 additions & 0 deletions option_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -463,3 +463,39 @@ func TestOptionScanner(t *testing.T) {
is.NoError(err2)
is.EqualValues(None[SomeScanner](), noneScanner)
}

// TestOptionFoldSuccess tests the Fold method with a successful result.
func TestOptionFoldSuccess(t *testing.T) {
is := assert.New(t)
option := Option[int]{isPresent: true, value: 10}

successFunc := func(value int) string {
return fmt.Sprintf("Success: %v", value)
}
failureFunc := func(err error) string {
return fmt.Sprintf("Failure: %v", err)
}

folded := Fold[error, int, string](option, successFunc, failureFunc)
expected := "Success: 10"

is.Equal(expected, folded)
}

// // TestOptionFoldFailure tests the Fold method with a failure result.
func TestOptionFoldFailure(t *testing.T) {
is := assert.New(t)
option := Option[int]{isPresent: false}

successFunc := func(value int) string {
return fmt.Sprintf("Success: %v", value)
}
failureFunc := func(err error) string {
return fmt.Sprintf("Failure: %v", err)
}

folded := Fold[error, int, string](option, successFunc, failureFunc)
expected := fmt.Sprintf("Failure: %v", optionNoSuchElement)

is.Equal(expected, folded)
}
15 changes: 15 additions & 0 deletions result.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,18 @@ func (o *Result[T]) UnmarshalJSON(data []byte) error {
o.isErr = false
return nil
}

// leftValue returns the error if the Result is an error, otherwise nil
func (r Result[T]) leftValue() error {
return r.err
}

// rightValue returns the value if the Result is a success, otherwise the zero value of T
func (r Result[T]) rightValue() T {
return r.value
}

// hasLeftValue returns true if the Result represents an error state.
func (r Result[T]) hasLeftValue() bool {
return r.isErr
}
10 changes: 10 additions & 0 deletions result_example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,3 +346,13 @@ func ExampleResult_FlatMap_err() {
fmt.Println(result.IsError(), result.OrEmpty(), result.Error())
// Output: true 0 error
}

func exampleResult_Fold() {
res := Result[int]{value: 42, err: nil}
foldResult := Fold[error, int, string](res, func(v int) string {
return "Success"
}, func(_ error) string {
return "Failure"
})
println(foldResult)
}
39 changes: 39 additions & 0 deletions result_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mo

import (
"encoding/json"
"errors"
"fmt"
"testing"

Expand Down Expand Up @@ -294,3 +295,41 @@ func TestResultUnmarshalJSON(t *testing.T) {
err = json.Unmarshal([]byte(`{"Field": "}`), &unmarshal)
is.Error(err)
}

// TestResultFoldSuccess tests the Fold method with a successful result.
func TestResultFoldSuccess(t *testing.T) {
is := assert.New(t)
result := Result[int]{value: 42, isErr: false, err: nil}

successFunc := func(value int) string {
return fmt.Sprintf("Success: %v", value)
}
failureFunc := func(err error) string {
return fmt.Sprintf("Failure: %v", err)
}

folded := Fold[error, int, string](result, successFunc, failureFunc)
expected := "Success: 42"

is.Equal(expected, folded)
}

// TestResultFoldFailure tests the Fold method with a failure result.
func TestResultFoldFailure(t *testing.T) {
err := errors.New("result error")
is := assert.New(t)

result := Result[int]{value: 0, isErr: true, err: err}

successFunc := func(value int) string {
return fmt.Sprintf("Success: %v", value)
}
failureFunc := func(err error) string {
return fmt.Sprintf("Failure: %v", err)
}

folded := Fold[error, int, string](result, successFunc, failureFunc)
expected := fmt.Sprintf("Failure: %v", err)

is.Equal(expected, folded)
}

0 comments on commit ef47e28

Please sign in to comment.