Skip to content

Feature Request: Let Pipe function take more than 1 positional argument. #1692

@PratikBhusal

Description

@PratikBhusal

Feature Request/Enhancement

I stumbled upon this post a while ago:

python/typing#1245

It was pretty neat, and wanted to try it out for functions that take more than 1 positional argument. Sadly, it does not seem to work.

What's wrong

When trying to run the following code:

from returns._internal.pipeline.pipe import pipe


def one_arg_only(num1: float) -> int:
    return int(num1)


def two_args(num1: float, num2: int) -> int:
    return int(num1 + num2)


def to_string(f: float) -> str:
    return str(f)


def to_float(s: str) -> float:
    return float(s)


if __name__ == "__main__":
    fizz = pipe(one_arg_only, to_string, to_float)
    print(fizz(1))  # Trivial example

    buzz = pipe(to_string, to_float)
    print(buzz(two_args(1, 2)))  # This works, but is not ideal

    bazz = pipe(two_args, to_string, to_float)
    try:
        print(bazz(1, 2))  # Too many arguments for "__call__" of "_Pipe" [call-arg]
    except TypeError as e:
        print(
            "Cannot pass two arguments even though first function requires 2 positional arguments"
        )
        raise e

you would get the following exception:

Cannot pass two arguments even though first function requires 2 positional arguments
Traceback (most recent call last):
  File "/home/pratik/workplace/dry-python-returns/problem.py", line 21, in <module>
    raise e
  File "/home/pratik/workplace/dry-python-returns/problem.py", line 16, in <module>
    print(fizz(1, 2))
          ^^^^^^^^^^
TypeError: pipe.<locals>.<lambda>() takes 1 positional argument but 2 were given

How is that should be

Invoking bazz should not throw a TypeError.

What I have tried

The following works if and only if the types are all correct.

from functools import reduce
from typing import overload, ParamSpec, TypeVar, Callable


_P = ParamSpec("_P")
_T1 = TypeVar("_T1")
_T2 = TypeVar("_T2")
_T3 = TypeVar("_T3")

@overload
def pipe(f1: Callable[_P, _T1]) -> Callable[_P, _T1]: ...
@overload
def pipe(
    f1: Callable[_P, _T1],
    f2: Callable[[_T1], _T2],
) -> Callable[_P, _T2]: ...
@overload
def pipe(
    f1: Callable[_P, _T1],
    f2: Callable[[_T1], _T2],
    f3: Callable[[_T2], _T3],
) -> Callable[_P, _T3]: ...

def pipe(*functions):
    def compose2(f, g):
        return lambda *args, **kwargs: g(f(*args, **kwargs))

    return reduce(compose2, functions)

When the types are not correct, mypy throws the following error message:

# Mypy error says: Cannot infer type argument 3 of "pipe"
fizz = pipe(one_arg_only, to_string, to_string)

I toyed around with typing.ParamSpec within returns/_internal/pipeline/pipe.pyi to see if that would do the trick, but I couldn't after a couple of hours of getting the above example to have mypy return no errors. Maybe I missed something, or I just have mind block.

Related issue: python/typing#1289

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions