Code Monkey home page Code Monkey logo

Comments (23)

Suor avatar Suor commented on May 24, 2024 19

I am not a big fan of adding types to funcy.

from funcy.

cardoso-neto avatar cardoso-neto commented on May 24, 2024 7

I believe the extended function semantics could be supported with @overload annotations.
Something along the lines of

from __future__ import annotations

from typing import Callable, Iterable, Mapping, TypeVar, overload

X = TypeVar("X")
Y = TypeVar("Y")


@overload
def funcy_map(f: None, iter_: Iterable[X]) -> Iterable[X]: ...

@overload
def funcy_map(f: Mapping[X, Y], iter_: Iterable[X]) -> Iterable[Y]: ...

@overload
def funcy_map(f: Callable[[X], Y], iter_: Iterable[X]) -> Iterable[Y]: ...


def funcy_map(f, iter_):
    if isinstance(f, type(None)):
        return iter_
    if isinstance(f, Mapping):
        return (f[x] for x in iter_)
    return (f(p) for p in iter_)

That being said, thanks for considering it and thank you for the awesome lib.
It would seem that a typeshed types-funcy package or maybe even a typed fork would indeed be the best course of action to avoid burdening you with even more code to maintain.

from funcy.

ruancomelli avatar ruancomelli commented on May 24, 2024 4

@julianfortune I have implemented some type-stubs locally and was thinking of eventually uploading them to typeshed once I got a complete set of annotations. Unfortunately, I've been in a lack of free time in the past months, so I didn't manage to make this ready for publishing.

Funnily enough, I happened to tweak those annotations yesterday after all of those months. I'm willing to get back to this project in the next few days, but don't refrain from doing it yourself if you wish 😁

from funcy.

ruancomelli avatar ruancomelli commented on May 24, 2024 4

Hi @Ayenem! After your message, I finally took the time to clean up my draft type-annotations and assemble them in a repository: https://github.com/ruancomelli/funcy-stubs.

Feel free to contribute, I would be glad to review and approve PRs!

@julianfortune you may be interested in taking a look at it as well.

Note that a good portion of it is still a draft, unsuitable for release. You can see the modules where I deliberately skipped some functions: I defined a __getattr__ function in them. In addition, many types can be further narrowed to provide a better type inference for the end users.

Coming soon is a better development setup: I'm planning on writing stubtests and setting up GitHub workflows.

from funcy.

Suor avatar Suor commented on May 24, 2024 3

A devoted maintainer is always better. I will link from README and docs once this is a thing.

Also it looks like a lot of repetition in stubs, multiplied by 2 if you'll want to distinguish iterator and list versions, i.e. -> List[X] and Iterator[X] for many seqs utils. May consider generating them.

from funcy.

julianfortune avatar julianfortune commented on May 24, 2024 2

@ruancomelli I don't think it makes sense to duplicate work, but if you want to make a repo with your work in progress, I would be happy to help out!

from funcy.

ahmed-moubtahij avatar ahmed-moubtahij commented on May 24, 2024 2

Perfect :) Thanks @ruancomelli I'll give it a look

from funcy.

ruancomelli avatar ruancomelli commented on May 24, 2024 1

Besides code completion, as mentioned by @cardoso-neto, type hints help to ensure code correctness even before execution. Moreover, when programming in typed contexts, funcy does not integrate well and often demands some kind of reworking: I either type-annotate everything manually or ask mypy to skip certain lines or (what I am getting more inclined to do) write stubs for myself.

However, I'm pretty sure that you are already aware of the benefits of adding type annotations everywhere, and the cons of not having them. Now, what are the annoyances of adding them to funcy? I can think of:

  1. possible compatibility issues: We have to make sure that we will not break older Python versions. There are many workarounds for this, one of them being including stub files (.pyi) together with the regular files (.py) for the sake of pleasing type-checkers. Those files are ignored at runtime, so no problems here. This is the approach followed by more-itertools, for instance.
  2. increased workload for further developments: In addition to implementing the desired functionality, contributors have to think about typing as well. While I agree that it is indeed a bit more work, I believe this is acceptable.
  3. possible mismatch between implementation and stubs: If a contributor changes the signature of a function in the implementation files, but forgets to update the stub files, there will be a mismatch. However, AFAIK type-checkers can be used here to make sure everything is still fine.
  4. writing all of those stub files will take a lot of time right now: Yes, I strongly agree with you here. But then we can always work on a separate branch and only add it to main once we complete this task.

Is there any other reason why you are not a fan of adding types to funcy?

from funcy.

cardoso-neto avatar cardoso-neto commented on May 24, 2024 1

Sometimes, yes. But in cases where the returned type is conditioned on the type of the input, overloading is a must. Otherwise invalid types would be inferred:

def funcy_map(
    f: Callable[[X], Y] | Mapping[X, Y] | None,
    iter_: Iterable[X],
) -> Iterable[X] | Iterable[Y]:
    ...

result = funcy_map({1:'d'}, [1, 2, 3])
for x in result:
    x  # int | str

Here all the types I had above overloaded are "unionized", but mypy has no idea if it returned identities or what and evaluates the type of x as the union.
So in this case, the verbosity is worth it.

from funcy.

Suor avatar Suor commented on May 24, 2024 1

It's not -> Iterable[X] | Iterable[Y] it's just -> Iterable[Y] for the most part. Only None is the issue.

from funcy.

cardoso-neto avatar cardoso-neto commented on May 24, 2024 1

There's also sets, which would return Iterable[bool]. And slices which would return Iterable[tuple[Y, ...]] if I'm not mistaken.

from funcy.

bersbersbers avatar bersbersbers commented on May 24, 2024 1

I am not a big fan of adding types to funcy.

I believe they should be added where possible, as adding funcy to typed code will break existing type checks. One example:

import numpy as np
from funcy import retry

@retry(3)
def load_data() -> np.typing.NDArray:
    return np.array([0])

# initialize
data: np.typing.NDArray | None = None

# do some work
data = load_data()
np.pad(data, 1)

mypy says

Argument 1 to "pad" has incompatible type "Optional[ndarray[Any, dtype[Any]]]"

The example works perfectly when commenting out @retry.

I believe typing the decorator aspect of retry might be as easy as

from typing import Callable, TypeVar
F = TypeVar("F", bound=Callable)
retry: Callable[..., Callable[[F], F]]

(but I may be missing something).

from funcy.

ruancomelli avatar ruancomelli commented on May 24, 2024 1

Hello, @bersbersbers!
Thanks for the feedback, I'll look into both issues and see if I can solve them in funcy-stubs. Also feel free to open any other typing-related issues in that repository.
Note that the type stubs in funcy-stubs is still incomplete, so a few issues are expected. If you have already correctly type-annotated a funcy function, please open a pull request in funcy-stubs 😉

from funcy.

cardoso-neto avatar cardoso-neto commented on May 24, 2024

Python's type hints can be lacking when it comes to higher-order functions.
However, it does make some things much, much easier.
For example, having decent code completion can only be attained with decent type hints.
It is very helpful. You should experiment with it, so you can feel its value for yourself.

from funcy.

Suor avatar Suor commented on May 24, 2024

Other issues with funcy is optional first arguments and passing different stuff instead of callables, see extended function semantics.

It's some extra code that's not really a code, which I will need to support if it goes here. I don't use type annotations so I won't spot something going out of sync naturally. Not sure whether there are automatic ways to do that though.

Anyway I would prefer someone else, who actually care about this, support type annotations. Probably outside of funcy repo, i.e. in typeshed.

from funcy.

Suor avatar Suor commented on May 24, 2024

Can't one use unions instead of overload? Should be less verbose. Overloading might still be needed for optional first args.

from funcy.

ruancomelli avatar ruancomelli commented on May 24, 2024

Can't one use unions instead of overload? Should be less verbose.
It's not -> Iterable[X] | Iterable[Y] it's just -> Iterable[Y] for the most part. Only None is the issue.

@Suor perfect; that is why overloads are necessary here, but for sure things can be less verbose. Taking only Nones, Callables and Mappings into account, we can simplify @cardoso-neto's implementation a bit:

@overload
def funcy_map(f: None, iter_: Iterable[X]) -> Iterable[X]: ...

@overload
def funcy_map(
    f: Callable[[X], Y] | Mapping[X, Y],
    iter_: Iterable[X]
) -> Iterable[Y]: ...

Unfortunately, it can't get much simpler than that because there is no way to annotate the return value as being something like Iterable[X] if f is None else Iterable[Y]. Whenever the return type changes depending on the inputs, we have to add a new overload.

I wrote a draft implementation for the basic type stubs for the extended function semantics, you can find it in my gist repository and add comments/suggestions at will. Note that it is an untested draft written for Python 3.9+, an actual implementation should target older Python versions (and be tested, of course!). The main problem I see here is that there are indeed lots of overloads - and I've only written stubs for two functions! The bright side is that new functions will probably be added as copy-paste-tune from those basic versions. We could also modularize things by creating custom generic types, but I'll leave that as an exercise for the reader :)

Anyway I would prefer someone else, who actually cares about this, support type annotations. Probably outside of funcy repo, i.e. in typeshed.

That is (almost) exactly what I was thinking. I'm not sure about the best way to publish this outside of funcy - I need to do some research first - but typeshed seems to be the way to go indeed. I am someone else who actually cares about this, and I would gladly write and maintain such a thing if you accept it; I'm just very busy at the moment, but perhaps in the next few weeks something could be done. What are your thoughts regarding this?

from funcy.

julianfortune avatar julianfortune commented on May 24, 2024

@ruancomelli Have you had a chance to start a project for typehints? Thanks!

from funcy.

ruancomelli avatar ruancomelli commented on May 24, 2024

Hey @julianfortune! It seems that I will not have enough time for this in the next few months, so please feel free to take on this project 😁

from funcy.

ahmed-moubtahij avatar ahmed-moubtahij commented on May 24, 2024

@julianfortune @ruancomelli Any of you can point me to the repo of the WIP on this? I could contribute here and there.

from funcy.

Suor avatar Suor commented on May 24, 2024

Did you try funcy-stubs linked above?

from funcy.

bersbersbers avatar bersbersbers commented on May 24, 2024

Did you try funcy-stubs linked above?

Only briefly, to be honest. It forces funcy==1.17,and even using that (pip install funcy-stubs), I still get

error: Call to untyped function "fallback" in typed context [no-untyped-call]

or

error: Untyped decorator makes function "load_data" untyped [misc]

from funcy.

bersbersbers avatar bersbersbers commented on May 24, 2024

A related problem that I have is that I cannot seem to type-annotate the imports myself, see python/mypy#15170

from funcy.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.