samber / mo

🦄 Monads and popular FP abstractions, powered by Go 1.18+ Generics (Option, Result, Either...)
https://pkg.go.dev/github.com/samber/mo
MIT License
2.47k stars 80 forks source link

Request for Extension of Result Monad to Accommodate Void Functions #36

Closed joaquinrovira closed 8 months ago

joaquinrovira commented 8 months ago

Context:

The current implementation of the Result monad in our functional programming library for Golang provides a robust mechanism for handling success and failure scenarios in functions. However, it lacks explicit support for functions that return no result on success, commonly known as "void" functions.

Proposal:

I propose extending the existing Result monad to include explicit support for void functions. This enhancement would enable users to represent and handle successful outcomes where the function does not explicitly return a result.

Requirements:

The extended Result monad should be able to accommodate both functions that return a value on success and functions that don't. This could involve introducing a new type or modifying the existing Result type to support both scenarios.

Use Case:

Consider a scenario where a function is responsible for updating a database record and does not return any value upon success. Currently, users have to resort to using nil or other ad-hoc solutions, which can lead to ambiguity and decreased code readability.

Example:

// Current Usage
func UpdateRecord(data Data) error {
    // perform update
    if err := performUpdate(data); err != nil {
        return err
    } 
    return nil
}

// Proposed Usage
func UpdateRecord(data Data) UnitResult {
    // perform update
    if err := performUpdate(data); err != nil {
        return Err(err)
    }
    return UnitOk() // No result to return on success
}

Benefits:

I look forward to community feedback and discussions on this proposed enhancement.

Thank you!

samber commented 8 months ago

Your proposal would be to create UnitResult, as an alias for Result[Unit]? Or a completely different monad type?

Can you provide some examples of UnitResult being much easier to use than err == nil?

joaquinrovira commented 8 months ago

Response

TL;DR: Disregard, after doing some more reading I realize the current version of Go does not accomodate what I am trying to achieve.

I am relatively new to FP concepts and the use of Monads, so there might be better ways to tackle the issue that I am not seeing. The scenario that led me to post this issue is when chaining function calls in and I want to make use of the build-in error handling of Result. Given the following functions:

type A struct { ... }
type B struct { ... }

func GetA() (A, error) { ... }
func UseA(a A) error { ... }
func GetB() (B, error) { ... }
func UseB(b B) error { ... }

func DoWork() error {
    a, err := GetA()
    if err != nil {
        return err
    }
    err = UseA(a)
    if err != nil {
        return err
    }
    b, err := GetB()
    if err != nil {
        return err
    }
    err = UseB(b)
    if err != nil {
        return err
    }

    return nil
}

func main() {
    err := DoWork()
    // ...
}

I would like to use the Result monad to more succinctly express the DoWork() function - avoiding the need for all those explicit if err != nil checks. Ideally I would like to write something similar to:

func DoWork() UnitResult {
    return Try(GetA).
        MapUnit(UseA).  // func (r Result[T])  MapUnit(mapper func(value T) error) UnitResult
        Map(GetB).      // func (r UnitResult) Map[T2 any](mapper func(value T) (T2, error)) Result[T2]
        MapUnit(UseB)   // func (r Result[T])  MapUnit(mapper func(value T) error) UnitResult
}

After doing some research I have come to realize that struct methods do not allow for generics so this would be impossible to implement right now. (https://github.com/golang/go/issues/49085)

samber commented 8 months ago

Oh! Yes, unfortunately, this is the major limitation of this library.

Trust me, as soon as it is possible in go, this library will get a v2 😆

Unfortunately, some dude in the Go core team don't want to push it forward :-(