dry-python / dry-python/returns

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

Abierto
#1,692 1 comentario 3 reacciones 0 asignados Ver en GitHub
Lenguaje dominante
Python
Estrellas
4.4k
Forks
155
Merge medio
2 h 27 min
PR fusionados (30 d)
22

Descripción

# Feature Request/Enhancement

I stumbled upon this post a while ago:

https://github.com/python/typing/discussions/1245?sort=top#discussioncomment-3454192

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:
```python
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:

```python
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
raise e
File "/home/pratik/workplace/dry-python-returns/problem.py", line 16, in
print(fizz(1, 2))
^^^^^^^^^^
TypeError: pipe..() 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.

```python
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:

```python
# 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: https://github.com/python/typing/discussions/1289

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.