Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

Composing 0-argument functions

Đang mở
#2,365 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
52/100
Loại issue
Tính năng
Độ rõ ràng
Khá rõ ràng
Mức độ hoạt động
Ít trao đổi
Công nghệ
python
Lĩnh vực
tooling

Hướng nghiên cứu

Bắt đầu tại điểm vào returns.functions.compose và so sánh hành vi hiện tại với một đối số với các ví dụ không có đối số trong issue. Xem lại cách kiểu của các callable hiện có được biểu đạt và cách các hàm đã tổ hợp được kiểm thử. Được xem là hoàn thành khi các callable không có đối số được tổ hợp một cách lazy, trong khi phép tổ hợp hiện có với một đối số vẫn tương thích và hành vi này được bao phủ bởi các bài kiểm thử.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

I have a need to set up a function composition which is both lazy and takes zero arguments. Here is a somewhat contrived example from before I realized functions made by compose take exactly one argument no matter what:

import operator
import random

import returns.pointfree as fp
from returns.curry import partial
from returns.functions import compose
from returns.maybe import maybe

might_int = lambda: None if random.randint(0, 1) else 1

compose(
    maybe(might_int),
    pf.map_(partial(operator.add, 1))
)()  # Expected: <Nothing> or <Some(2)>

# TypeError: compose.<locals>.<lambda>() missing 1 required positional argument: 'argument'

This example could easily be reworked with flow, of course. However, there may be cases where zero-argument functions need to be composed: any instance method with self as its only parameter, Context.get, partial or curried functions that are being passed around or reused in lots of places for their laziness, or any pattern that relies on "thunks" (trampolines and continuation passing come to mind).

(The actual use-case I have is a bit more complicated, involving decorator factories and such, and while I was typing up a description of it I found that the decorator factory already handles the very specific thing I was trying to do and I didn't need compose at all. I wrote that factory almost two years ago and completely forgot 😅 All the same, I feel like this is a gap in returns.)

I can think of three ways to accommodate this usage:

# Add a separate function just for this:
def compose0(
    first: Callable[[], _SecondType],
    second: Callable[[_SecondType], _ThirdType],
) -> Callable[[], _ThirdType]:
    return lambda: second(first())

# Or, trade a couple microseconds for some flexibility:
import inspect

def compose[_ThirdType, **P](
    first: Callable[P, _SecondType],
    second: Callable[[_SecondType], _ThirdType],
) -> Callable[P, _ThirdType]:
    param_count = len(inspect.signature(func).parameters)  # May not always work; see edit below.
    if param_count == 1:
        return lambda argument: second(first(argument))
    elif param_count == 0:
        return lambda: second(first())
    else:
        # This helps by raising an error sooner, i.e. as soon as `compose` is called rather than when
        # the produced lambda is called. This _could_ be the difference between catching a bug 
        # when the application starts instead of during runtime, having slipped through tests.
        raise TypeError("The first function given to compose must be a 0- or 1-argument callable.")

# Or, total flexibility, and probably a strain on type checkers:

def compose[_ThirdType, **P](
    first: Callable[P, _SecondType],
    second: Callable[[_SecondType], _ThirdType],
) -> Callable[P, _ThirdType]:
    def _composed(*args: P.args) -> _ThirdType:
        return second(first(*args))

    return _composed

Edit to add one aside:

There's an additional (if minor) advantage with the form of the third option--that is, returning a def function rather than a lambda function: Lambdas cannot be pickled as of Python 3.13, but regular functions can be. Most users are probably not bothered by this, admittedly, but it could become a complication for applications that also use eg. Celery, which has the option of using pickle as its data transfer protocol between actors.

One more:

I discovered that inspect.signature doesn't always play nicely with functions that are implemented in C and don't provide metadata. ContextVar.get happens to be one such function. I haven't yet found a good way to check if a function can be inspected this way other than to wrap it in a try/except.

Ngôn ngữ chính
Python
Star
4.4k
Fork
154
Merge trung bình
3 giờ 5 phút
Pull request đã merge (30 ngày)
22

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của dry-python/returns

Tất cả issue của dry-python/returns

Issue tương tự

Thêm issue về Python

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.