dbrattli / Expression

Pragmatic functional programming for Python inspired by F#
https://expression.readthedocs.io
MIT License
421 stars 30 forks source link

Is there some helper function to use parameters for function in Result? #116

Closed phi-friday closed 1 year ago

phi-friday commented 1 year ago

Is your feature request related to a problem? Please describe. There are times when I have to call a function in an uncertain state, and I use it in the following way.

import expression as ex

def func(a: int, b: str):
    return [a, b]

def get_func():
    return ex.Ok(func)

func_result = get_func()
value = func_result.map(lambda f: f(1, "test"))

It doesn't matter if it's a simple case. But, if it overlaps a little bit, it's quite a hassle.

Describe the solution you'd like I think there are two ways

import expression as ex

def func(a: int, b: str):
    return [a, b]

def get_func():
    return ex.Ok(func)

func_result = get_func()
value = helper_func(func_result, 1, "asd")

or

import typing as tp
import expression as ex

def func(a: int, b: str, c: ex.Result[int, tp.Any]):
    return [a, b]

def get_func():
    return ex.Ok(func)

func_result = get_func()

value = ex.pipe(
    func_result,
    func_helper.map(1),
    func_helper.map("asd"),
    func_helper.bind(123),
)

Describe alternatives you've considered I'm using the second method as follows, but I don't think it's pythonic.

do not use below, too many bugs.

from functools import partial
from typing import Any, Callable, Union

from expression import Ok, Result, pipe
from expression.extra.result import catch
from typing_extensions import Concatenate, ParamSpec, TypeVar

ArgT = TypeVar("ArgT")
ParamT = ParamSpec("ParamT")
ResultT = TypeVar("ResultT")

class Currylike:
    @classmethod
    def map(
        cls, arg: ArgT
    ) -> Callable[
        [
            Union[
                Callable[Concatenate[ArgT, ParamT], ResultT],
                Result[Callable[Concatenate[ArgT, ParamT], ResultT], Any],
            ]
        ],
        Result[Callable[ParamT, ResultT], Any],
    ]:
        return partial(cls.fmap, arg)

    @classmethod
    def bind(
        cls, arg: ArgT
    ) -> Callable[
        [
            Union[
                Callable[Concatenate[Result[ArgT, Any], ParamT], ResultT],
                Result[Callable[Concatenate[Result[ArgT, Any], ParamT], ResultT], Any],
            ]
        ],
        Result[Callable[ParamT, ResultT], Any],
    ]:
        return partial(cls.fbind, arg)

    @staticmethod
    def fmap(
        arg: Union[ArgT, Result[ArgT, Any]],
        func: Union[
            Callable[Concatenate[ArgT, ParamT], ResultT],
            Result[Callable[Concatenate[ArgT, ParamT], ResultT], Any],
        ],
    ) -> Result[Callable[ParamT, ResultT], Any]:
        if isinstance(arg, Result):
            if isinstance(func, Result):
                return func.bind(lambda f: arg.map(lambda value: partial(f, value)))  # type: ignore

            return arg.map(lambda value: partial(func, value))  # type: ignore

        if isinstance(func, Result):
            return func.map(lambda f: partial(f, arg))  # type: ignore
        return Ok(partial(func, arg))  # type: ignore

    @staticmethod
    def fbind(
        arg: Union[ArgT, Result[ArgT, Any]],
        func: Union[
            Callable[Concatenate[Result[ArgT, Any], ParamT], ResultT],
            Result[Callable[Concatenate[Result[ArgT, Any], ParamT], ResultT], Any],
        ],
    ) -> Result[Callable[ParamT, ResultT], Any]:
        if isinstance(arg, Result):
            if isinstance(func, Result):
                return func.map(lambda f: partial(f, arg))  # type: ignore
            return Ok(partial(func, arg))  # type: ignore

        if isinstance(func, Result):
            return func.map(lambda f: partial(f, Ok(arg)))  # type: ignore
        return Ok(partial(func, Ok(arg)))  # type: ignore

    @staticmethod
    def fcall(
        func: Result[Callable[ParamT, ResultT], Any],
        *args: ParamT.args,
        **kwargs: ParamT.kwargs
    ) -> Result[ResultT, Any]:
        return func.bind(lambda f: catch(exception=Exception)(f)(*args, **kwargs))

def func(a: int, b: str, c: ex.Result[int, tp.Any]):
    return [a, b]

def get_func():
    return Ok(func)

func_result = get_func()

value = pipe(
    func_result,
    Currylike.map(1),
    Currylike.map("asd"),
    Currylike.bind(123),
    Currylike.fcall
)

I would appreciate it if you could let me know if there is a good method that you have already provided.

Additional context Add any other context or screenshots about the feature request here.

phi-friday commented 1 year ago

pr #149