IBM / fp-go

functional programming library for golang
Apache License 2.0
1.63k stars 47 forks source link

missing Match function on Either type #54

Closed xujihui1985 closed 1 year ago

xujihui1985 commented 1 year ago

I wander how can I handle the either type, say in the main function I want to print the error and exit if val is left or log the value if it is right, can we introduce a Match function in Either ? Or there is better way to do that?

func main() {
  name := GetName(NewThing("hello"))
// Match on Either
  Match(name,
    func(err error) {
        log.Fatal("failed err %v \n", err)
    },
    func(val string) {
        fmt.Printf("get val %s \n", val)
    },
  )
}

func Match[T any, V any](e E.Either[T, V], l func(t T), r func(v V)) {
    a, b := E.Unwrap(e)
    if E.IsLeft(e) {
        l(b)
    } else {
        r(a)
    }
}

func GetName(val O.Option[Thing]) E.Either[error, string] {
    return O.Fold(
        F.Constant[E.Either[error, string]](E.Left[string](errors.New("val is none"))),
        getName,
    )(val)
}

func getName(val Thing) E.Either[error, string] {
    if val.Name == "" {
        return E.Left[string](errors.New("name is not empty"))
    }
    return E.Right[error](val.Name)
}
CarstenLeue commented 1 year ago

Hi @xujihui1985

the match function for Either is named Fold.

You could e.g. write your example like so (avoiding explicit branching and using point-free style:

import (
    "fmt"

    E "github.com/IBM/fp-go/either"
    "github.com/IBM/fp-go/errors"
    F "github.com/IBM/fp-go/function"
    O "github.com/IBM/fp-go/option"
    S "github.com/IBM/fp-go/string"
)

type Thing struct {
    Name string
}

func (t Thing) GetName() string {
    return t.Name
}

var (
    // func(Thing) Either[error, string]
    getName = F.Flow2(
        Thing.GetName,
        E.FromPredicate(S.IsNonEmpty, errors.OnSome[string]("value [%s] is empty")),
    )

    // func(option.Option[Thing]) Either[error, string]
    GetName = F.Flow2(
        E.FromOption[Thing](errors.OnNone("value is none")),
        E.Chain(getName),
    )
)

func ExampleEither_match() {

    oThing := O.Of(Thing{"Carsten"})

    res := F.Pipe2(
        oThing,
        GetName,
        E.Fold(S.Format[error]("failed with error %v"), S.Format[string]("get value %s")),
    )

    fmt.Println(res)

    // Output:
    // get value Carsten

}

Judging based on your example it looked like NewThing("hello") returned an Option[Thing]. In your example you then lift that Option into an Either.

In case the underlying usecase is to select an optional Name attribute from an optional Thing you could also consider to stay with the Option type all along and just use O.Chain.

xujihui1985 commented 1 year ago

thank you for show me the idiomatic way to doing that. That's helpful.