Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Fold function to Result monad #42

Merged
merged 7 commits into from
Jun 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update to newer version as I got the following linting error

func Result[T].{{func_name}} is unused (unused)

regarding those functions

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