tandav / pipe21

Simple functional pipes for python
https://tandav.github.io/pipe21
MIT License
18 stars 1 forks source link

Typing? #47

Open james-pcdr opened 3 months ago

james-pcdr commented 3 months ago

Hello, I've been exploring ways to incorporate FP in Python, and I like the syntax of this project. The only downside (IMO) is the lack of type annotations.

Would you be interested in typing-related PRs?

(If the answer is yes, I'll follow-up questions about the details, such as whether to incorporate the existing mypy-fixes branch.)

Thanks!

tandav commented 3 months ago

@james-pcdr

Thank you for your interest! Yes, type annotations are something that is missing and would be great to have.

I've tried to add type annotations. My attempts are on the mypy-fixes branch, but it's quite messy and unstructured. I've pushed a new branch mypy-mvp with a minimal example that shows the problem I am stuck at. This minimal example adds type annotations to the classes B (base class), Pipe, Map, and Filter. The error I encountered is documented in the README of that branch.

I don't know Python types well enough to solve this error. Answers from ChatGPT and Claude didn't help. I think solving this minimal example problem is a good starting point.

One of the goals for this project is to have simple methods, preferably one-liners (if possible, although this requirement can be relaxed). To achieve this, I've put type annotations in a separate pipe21.pyi stub file instead of the file with actual implementations.

Feel free to ask any follow-up questions, and PRs are welcome!

james-pcdr commented 3 months ago

Thanks for the detailed response!

A few thoughts/disclaimers:

More to follow another day.

james-pcdr commented 3 months ago

I have a few inter-related comments/questions.

  1. Context: Here's the workaround that I've been using so far in my personal usage:
class Map(Generic[T, U]):
    def __init__(self, f: Callable[[T], U], ta: Optional[Type[T]] = None):
        self.f = f

    def __ror__(self, x: Iterable[T]) -> Iterator[U]:
        return map(self.f, x)

The disadvantage is it requires stating the type in places where mypy struggles, e.g.

["Tuesday", "cheese"] | Map(lambda x: x.endswith("day"), str)
#                                          explicit type ^^^ 

However, it does have one benefit: you get editor autocompletion as long as you type the second arg first:

Map(lambda x: x.endswi , str)
#                     ^  imagine the cursor here
  1. Question: Are you interested in incorporating such a workaround, or would that go against the goal of simplicity? (Quite understandable if so!)
  2. Follow-up question: I am considering creating a Github fork of pipe21 until such time that we figure out a proper solution (if such exists). Do you have an opinion on that and/or a license file? (Just Github, not PyPI -- I'd ask you for your opinion again if I later had an interest in publishing the fork on PyPI.)
tandav commented 3 months ago
  1. Yes, the workaround with extra type argument for Map looks complicated to me.
  2. No problem with fork or upload fork to pypi. I've pushed the MIT license to master.
james-pcdr commented 2 months ago
  1. :+1:
  2. Thanks!

After some experimentation with mypy, pytype, and pylyzer, I've decided to wait for the Python typing ecosystem to get better before tackling this. Sorry that I didn't find an answer, and feel free to close this issue (or not, in case you want it to remain as an invitation to others).

james-pcdr commented 2 months ago

A few follow-up thoughts:

  1. The example you posted works with mypy if you "de-genericize" frozenset:
def types_map_pipe_frozenset(x: str) -> frozenset[int]:
    def frozenset_int(it: Iterable[int]) -> frozenset[int]:
        return frozenset(it)

    return x | Map(int) | Pipe(frozenset_int)
  1. If you aren't opposed, we could, over time, add more types to your .pyi file.