Hello everyone,
Several weeks ago I have presented you typed `partial` function as a part
of `dry-python/returns` project.
But, why should we stop there? Now, I am happy to present fully typed
`@curry` decorator for your functions. However, there are several issues
with instance and class methods I would like to be helped with.
Implementation:
https://github.com/orsinium-forks/returns/blob/curry/returns/curry.py#L38
Plugin:
https://github.com/orsinium-forks/returns/blob/0b3d0ab3204e6732b993aae76920…
Tests:
https://github.com/orsinium-forks/returns/blob/curry/tests/test_curry/test_…
Typetests:
https://github.com/orsinium-forks/returns/tree/curry/typesafety/test_curry/…
Here's how it works:
from returns.curry import curry
@curry
def func(a: int, b: str, c: float) -> bool:
...
reveal_type(func)
reveal_type(func(1))
reveal_type(func(1)('a'))
reveal_type(func(1, 'a'))
Output:
ex.py:1: note: Revealed type is '
Overload(
def (a: builtins.int) -> Overload(
def (b: builtins.str, c: builtins.float) -> builtins.bool,
def (b: builtins.str) -> def (c: builtins.float) -> builtins.bool
),
def (a: builtins.int, b: builtins.str) -> def (c: builtins.float) ->
builtins.bool,
def (a: builtins.int, b: builtins.str, c: builtins.float) ->
builtins.bool
)'
ex.py:2: note: Revealed type is '
Overload(
def (b: builtins.str, c: builtins.float) -> builtins.bool,
def (b: builtins.str) -> def (c: builtins.float) -> builtins.bool
)'
ex.py:3: note: Revealed type is 'def (c: builtins.float) -> builtins.bool'
ex.py:4: note: Revealed type is 'def (c: builtins.float) -> builtins.bool'
Basically, we generate a deeply nested tree of all possible argument paths
and then build Overloaded cases for each of them. Here's the interesting
part: function `func` can be called with `a` as the first and the only
argument. In this case we will choose this overload:
def (a: builtins.int) -> Overload(
def (b: builtins.str, c: builtins.float) -> builtins.bool,
def (b: builtins.str) -> def (c: builtins.float) -> builtins.bool
)
Which is cool!
It works with:
- regular functions
- generic functions
- unbound methods
- staticmethods
You can check out our typetests here:
https://github.com/orsinium-forks/returns/tree/curry/typesafety/test_curry/…
But there are known issues at the moment with bound methods. Let me show
you the problem. For example, we can take this piece of code:
class Test(object):
@curry
def some(self, a: int, b: str) -> float:
...
Let's think of what type should `Test().some` have, it should be an
overload of:
- (a: int) -> (b:str) -> float
- (a:int, b: str) -> float
That's what we have instead.
Our plugin gives an incorrect result (because we cannot know if this is a
function or method at the moment):
Overload(
def (self: ex.Test) -> Overload(
def (a: builtins.int, b: builtins.str) -> builtins.float,
def (a: builtins.int) -> def (b: builtins.str) -> builtins.float
),
def (self: ex.Test, a: builtins.int) -> def (b: builtins.str) ->
builtins.float,
def (self: ex.Test, a: builtins.int, b: builtins.str) -> builtins.float
)
Notice that it has an extra `self` argument.
But, mypy reveals a completely different type:
reveal_type(Test().some)
ex.py:10: note: Revealed type is '
Overload(
def () -> Overload(
def (a: builtins.int, b: builtins.str) -> builtins.float,
def (a: builtins.int) -> def (b: builtins.str) -> builtins.float
),
def (a: builtins.int) -> def (b: builtins.str) -> builtins.float,
def (a: builtins.int, b: builtins.str) -> builtins.float
)'
Looks like it removes `self` argument from all cases to be similar to what
Python does.
So, my questions are:
1. How can I know if this is a method or function from `CallableType` or
`FunctionContext`?
2. Is removing the first `self` / `cls` argument with
`mypy.typeops.bind_self` good enough for this usecase? Or just dropping it
from `arg_types` / `arg_names` is better?
3. Are there any useful APIs to help me with this problem?
I would really appreciate your help!
Failing CI with real tests cases and error logs:
https://github.com/dry-python/returns/runs/653833953?check_suite_focus=true…
Upstream: https://github.com/dry-python/returns
Thanks a lot for your work on mypy project! It is awesome!
Best,
Nikita Sobolev
https://github.com/sobolevn
Given recent usability improvements in type hints (PEPs 585, 604), I
thought I'd raise this minor issue which I find relevant as an
instructor.
Given this signature:
def f(a: float, b: Optional[int]) -> float:
The `b` parameter is not optional. The caller must always provide an
actual argument as an integer or None. To make `b` optional, we must
write:
def f(a: float, b: Optional[int] = None) -> float:
PEP 604 syntax avoids the misleading name `Optional`, which is progress:
def f(a: float, b: int | None = None) -> float:
But now another minor issue is highlighted: the repetition of None.
That's unfortunate given that almost all uses of None in parameters
are precisely as default values for optional parameters—that the
caller doesn't need to provide.
I wonder if the authors of the typing PEPs considered these issues, or
if there are ideas to fix them.
I don't have a proposal at this time. I wanted to start the
conversation to see if we can find a fix, or at least learn to live
with the current status—which is not bad, but also not excellent in
terms of readability.
Cheers,
Luciano
--
Luciano Ramalho
| Author of Fluent Python (O'Reilly, 2015)
| http://shop.oreilly.com/product/0636920032519.do
| Technical Principal at ThoughtWorks
| Twitter: @ramalhoorg
Hello all,
I recently wrote an updated proposal about breaking up typeshed into
multiple PyPI packages, starting from this comment:
https://github.com/python/typeshed/issues/2491#issuecomment-611607557
I and Ivan Levkivskyi expect to have some time to work on the
implementation in May/June if we can agree on a spec. Let's continue the
discussion in the issue linked to above.
Jukka