Skip to main content

pytyped-curry

Function currying that can be statically typed.

Functional tool for currying a function. Currying a function makes the function callable multiple times before the function is actually ran. Use curry(n)(func) or @curry(n) to transform a function into a curried function which takes n calls before running.

Imports

from pytyped_curry import curry
from pytyped_curry import decorator  # python >= 3.9

Example

# Transform the function into a curried function that takes
# two function calls before running.
@curry(2)
def add(x: int, y: int) -> int:
    return x + y

# Add needs to be called twice to be ran.
add(2)(3)  # 5

# Partial evaluation is easy.
increment = add(1)

increment(5)  # 6

# The two arguments accept multiple forms.
add(x=2)(y=3)
add(y=3)(x=2)
add(2, 3)()
add()(2, 3)

For Decorators

Often times writing decorators requires writing several nested functions. This is often a hassle, and in many cases completely unnecessary due to currying.

Note: reveal_type is ran using mypy.

from typing import Callable, TypeVar

T = typing.TypeVar("T")
RT = typing.TypeVar("RT")

@curry(2, ...)
def decorator(func: Callable[[T], RT], x: T) -> RT:
    print("Start")
    y = func(x)
    print("Finished")
    return y

reveal_type(decorator)
"""
def (def (T`-1) -> RT`-2) -> def (T`-1) -> RT`-2
"""

@decorator
def increment(x: int) -> int:
    return x + 1

reveal_type(increment)
"""
def (builtins.int) -> builtins.int
"""

@curry(3, ...)
def rate_limit(timeout: float, func: Callable[[T], RT], x: T) -> RT:
    time.sleep(timeout)
    return func(x)

reveal_type(rate_limit)
"""
def (builtins.float) -> (def (T`-1) -> RT`-2) -> def (T`-1) -> RT`-2
"""

@rate_limit(5)
def request_data(name: str) -> int:
    return len(name)

reveal_type(request_data)
"""
def (builtins.str) -> builtins.int
"""

Documentation

New in Python 3.9

Doc-strings can be applied to arbitrary objects at runtime for runtime use with the help(...) function. A few additional pieces of metadata are also accessible at runtime to provide clearer documentation, such as the name of the result.

@curry(3)
def add(x: int, y: int, z: int) -> int:
    """Returns add(x)(y)(z) = x + y + z."""
    return x + y + z

help(add)
"""
Help on Curried in module __main__:

add = curry(3)(add(x: int, y: int, z: int) -> int)
    Returns add(x)(y)(z) = x + y + z.

""""

help(add(1))
"""
Help on Curried in module __main__:

add(1) = curry(2)(add(x: int, y: int, z: int) -> int, 1)
    Returns add(x)(y)(z) -> x + y + z.

"""

help(add(1)(2))
"""
Help on Curried in module __main__:

add(1, 2) = curry(1)(add(x: int, y: int, z: int) -> int, 1, 2)
    Returns add(x)(y)(z) -> x + y + z.

"""

add(1)(2)(3)  # 6

Type-Hinting

New in Python 3.8

Type-hints for curried functions are nigh impossible in the general case, as can be seen by the last example. However, this doesn't stop us from enabling typing in many common use-cases. Curried functions are hinted as functions which take any arguments but take n calls, up to n = 3 for Python < (3, 11) and up to n = 4 otherwise. Although the arguments are not preserved, the final return type is.

Note: reveal_type is ran using mypy.

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

reveal_type(add)
"""
def (*Any, **Any) -> def (*Any, **Any) -> builtins.int
"""

For Python < (3, 11), one can also use curry(n, ...) to hint the curried function as taking exactly 1 positional argument per call, up to n = 3.

@curry(2, ...)
def add(x: int, y: int) -> int:
    return x + y

reveal_type(add)
"""
def (builtins.int) -> def (builtins.int) -> builtins.int
"""

For Python >= (3, 11), one can also use curry(n, ...) to hint the curried function as taking exactly 1 positional argument per call, up to n = 3, except for the last call. Notice that the y parameter is preserved as a positional-or-keyword parameter.

@curry(2, ...)
def add(x: int, y: int) -> int:
    return x + y

reveal_type(add)
"""
def (builtins.int) -> def (y: builtins.int) -> builtins.int
"""

For more precise hinting, one must use typing.cast around the currying function.

from typing import Protocol, overload


class AddEmpty(Protocol):

    def __call__(self) -> int:
        ...


class AddX(Protocol):

    def __call__(self, x: int) -> int:
        ...


class AddY(Protocol):

    def __call__(self, y: int) -> int:
        ...


class Add(Protocol):

    @typing.overload
    def __call__(self, x: int, y: int) -> AddEmpty:
        ...

    @typing.overload
    def __call__(self, x: int) -> AddY:
        ...

    @typing.overload
    def __call__(self, *, y: int) -> AddX:
        ...

    def __call__(self, x, y):
        ...


@typing.cast(Add, curry(2))
def add(x: int, y: int) -> int:
    return x + y

reveal_type(add)
"""
__main__.Add
"""

Metadata

Release files for pytyped-curry 1.0.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pytyped-curry 1.0.3
File Size Uploaded Provenance
pytyped-curry-1.0.3.tar.gz 8.7 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for pytyped-curry 1.0.3
File Interpreter ABI Platform
pytyped_curry-1.0.3-py3-none-any.whl Python 3 none any Details

Total release size: 24.2 kB

Release files / pytyped-curry-1.0.3.tar.gz

Download URL pytyped-curry-1.0.3.tar.gz
Size 8.7 kB
Tags Source
SHA-256 checksum
How to use checksums
bd7877443c6f6a92f4496903497371dc65700f88412aab5fef59bb36f14b19a0
BLAKE2b-256 checksum
How to use checksums
439088564b0cf5da9556f73008b931650091201449a083ff5b57577862d94d9f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.1 CPython/3.10.4

Release files / pytyped_curry-1.0.3-py3-none-any.whl

Download URL pytyped_curry-1.0.3-py3-none-any.whl
Size 15.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
61a692b698c631a5ffd586dcb371cde0dc06ca4f69c07d07effbab2df8a3411e
BLAKE2b-256 checksum
How to use checksums
24547cad62c998921a955d9e3af5d8bd560d03c1c7c6b9c88c9ed0c60d77bc6a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.1 CPython/3.10.4

Security for pytyped-curry 1.0.3

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page