在Python中键入curried函数

在Python中键入curried函数,python,functional-programming,Python,Functional Programming,咖喱有这样一个功能。问题是我不知道如何让这个函数返回一个具有正确类型的修饰函数。救命啊,我还没有找到解决办法 import functools import typing as ty from typing import TypeVar, Callable, Any, Optional F = TypeVar("F", bound=Callable[..., Any]) def curry(func: F, max_argc: Optional[int] = None): if ma

咖喱有这样一个功能。问题是我不知道如何让这个函数返回一个具有正确类型的修饰函数。救命啊,我还没有找到解决办法

import functools
import typing as ty
from typing import TypeVar, Callable, Any, Optional

F = TypeVar("F", bound=Callable[..., Any])

def curry(func: F, max_argc: Optional[int] = None):
    if max_argc is None:
        max_argc = func.__code__.co_argcount

    @functools.wraps(func)
    def wrapped(*args): 
        argc = len(args)
        if argc < max_argc:
            return curry(functools.partial(func, *args), max_argc - argc)
        else:
            return func(*args)
    return ty.cast(F, wrapped)

@curry
def foo(x: int, y: int) -> int:
    return x + y

foo("df")(5)  # mypy error: Too few arguments for "foo"
              # mypy error: "int" not callable
              # mypy error: Argument 1 to "foo" has incompatible type "str"; expected "int"  # True
导入工具
将键入导入为ty
输入import TypeVar、Callable、Any和Optional
F=TypeVar(“F”,bound=Callable[…,Any])
def curry(函数:F,最大值:可选[int]=None):
如果max_argc为无:
max_argc=func.\u代码\u.co\u argcount
@functools.wrapps(func)
def包装(*参数):
argc=len(args)
如果argcint:
返回x+y
foo(“df”)(5)#mypy错误:“foo”的参数太少
#mypy错误:“int”不可调用
#mypy错误:“foo”的参数1具有不兼容的类型“str”;应为“int”#真

如何修复1,2个mypy错误?

首先,我不会让它成为装饰程序,我会将它包装为
curry(foo)
。我发现在API中修饰的函数签名与其初始定义不同,这让人感到困惑

关于类型的主题,如果Python类型提示可以实现一般情况,我会印象深刻。我甚至不知道在Scala我会怎么做。对于以下两个参数的函数,可以使用
重载
,执行有限的情况

T1 = TypeVar("T1")
T2 = TypeVar("T2")
U = TypeVar("U")

@overload
def curry(
    func: Callable[[T1, T2], U],
    max_argc: Optional[int]
) -> Callable[[T1], Callable[[T2], U]]:
    ...

为一个、三个、四个参数等添加版本。不管怎么说,有很多参数的函数都是代码味道,除了varargs,我不确定它对curry是否有意义。

有趣的是,我尝试了完全相同的方法,编写了一个decorator,它将返回任何函数的curry版本,包括一般的高阶。 我试图构建一个
curry
,允许对输入参数进行任何分区

拒绝 然而,由于python类型系统的一些限制,这是不可能的。 我现在正在努力寻找一种通用的类型安全方法。我的意思是Haskell的生命,C++采用了它,Typescript也设法做到了,那么为什么Python不能?

我接触了
mypy
的备选方案,比如
pyright
,它有很棒的维护人员,但受PEP的约束,PEP声明有些事情是不可能的

愤怒 当我提交一个关于我的咖喱链中最后一块缺失的
pyright
的问题时,我将问题归结为以下几点(如本期所示:)

输入import TypeVar
从输入扩展导入协议
R=TypeVar(“R”,协变=True)
S=TypeVar(“S”,逆变=True)
类ArityOne(协议[S,R]):
定义调用(self,s:s)->R:
...
定义id_f(x:ArityOne[S,R])->ArityOne[S,R]:
返回x
X=类型变量(“X”)
def标识(x:x)->x:
返回x
i:int=id\u f(identity)(4)#不进行类型检查,应为'X'类型,获取的类型为'Literal[4]`
注意差距,这是一个最小的可复制的缺失链接的例子

我最初尝试做的是以下几点(跳过实际的
curry
实现,相比之下,这类似于在公园里散步):

  • 写一个
    curry
    decorator(不带类型)
  • 定义一元、二元和三元(等)
    协议
    s,这是函数类型
    可调用
    的更现代版本。巧合的是,
    协议
    s可以为它们的
    \uuuuu调用
    方法指定类型
    @重载
    ,这就引出了下一点
  • 使用
    协议
    s和类型
    @重载
    ed
    \u调用
    方法定义当前二进制、当前二进制(等)
  • curry
    函数定义类型
    @重载
    s,例如
    二进制->CurriedBinary
    三元->curriedbternary
  • 有了这个,一切都准备就绪了,对于固定类型的函数,也就是
    int->int
    str->int->bool
    ,它工作得非常好。 我现在手头上没有我尝试的实现

    但是,当对诸如
    map
    filter
    之类的函数进行curry时,它无法将函数的curry通用版本与实际类型相匹配

    讨价还价 这是由于类型变量的作用域是如何工作的。有关更多详细信息,您可以查看GitHub问题。这里有更详细的解释

    本质上,要处理的泛型函数的类型变量不受要部分传递的数据的实际类型的影响,因为在这两者之间有一个
    类协议,它定义了自己的类型变量范围

    试图包装或重组这些东西并没有产生丰硕的成果

    抑郁 我使用
    Protocol
    s来表示当前函数的类型, 例如,
    Callable
    ,这是不可能的,尽管
    pyright
    将重载函数的类型显示为
    重载[contract1,contract2,…]
    但没有这样的符号,只有
    @Overload

    因此,无论哪种方式,都有一些东西阻止你表达你想要的类型

    由于python类型系统的限制,目前无法表示完全通用的typesafe curry函数

    接受 但是,在某些特性上,如泛型或输入参数的任意分区,可能会有所妥协

    下面的
    curry
    实现在
    pyright 1.1.128
    中工作

    from typing import TypeVar, Callable, List, Optional, Union
    
    
    R = TypeVar("R", covariant=True)
    S = TypeVar("S", contravariant=True)
    T = TypeVar("T", contravariant=True)
    
    
    def curry(f: Callable[[T, S], R]) -> Callable[[T], Callable[[S], R]]:
        raise Exception()
    
    
    X = TypeVar("X")
    Y = TypeVar("Y")
    
    
    def function(x: X, y: X) -> X:
        raise Exception()
    
    def to_optional(x: X) -> Optional[X]:
        raise Exception()
    
    def map(f: Callable[[X], Y], xs: List[X]) -> List[Y]:
        raise Exception()
    
    
    i: int = curry(function)(4)(5)
    s: List[Optional[Union[str, int]]] = curry(map)(to_optional)(["dennis", 4])
    

    一个没有答案,另一个。你使用的是什么版本的mypy?您的代码在我的0.740上运行良好。我使用了mypy 0.761。谢谢您的回答。我就是这么做的。只为一个、三个、四个参数添加了重载。