dry-python / returns

Make your functions return something meaningful, typed, and safe!
https://returns.rtfd.io
BSD 2-Clause "Simplified" License
3.62k stars 119 forks source link

Library question #388

Closed dgjustice closed 4 years ago

dgjustice commented 4 years ago

Can you advise on this? I don't understand why the map isn't working at the bottom of the script. I'm taking a wrapped function get_ip_addr and maping it to a "pure" function (same as fmap, right?). This file is a very common pattern that I use, but I can't get mypy and returns to be happy. https://gist.github.com/dgjustice/257f7a2a2a8c67bc75bb1abcc8125f8f I am using the master branch.

Thanks for any advise!

sobolevn commented 4 years ago

Hi, @dgjustice!

Because when you are using the first example, you execute RequiresContext on the very same line:

(
        get_ip_addr()("https://whatsmyip.ovh/json")
       # now it is just `IOResultE[OVHIpAddr]`
        .map(format_city_country)  # works, because there's a single level of nesting
        .map(tap(print))
    )

But, with the second example you don't execute RequiresContext:

(
        get_ip_addr()
        # It is still `RequiresContext[IOResultE[OVHIpAddr]]`
        .map(format_city_country)  # value here will be `IOResultE[OVHIpAddr]`
        .map(tap(print))("https://whatsmyip.ovh/json")
    )

I would recommend to try RequiresContextIOResult here. It looks like that it will do exactly what you want. Report back, if you have any problems! 👍

dgjustice commented 4 years ago

I'm following the second example here https://returns.readthedocs.io/en/latest/pages/context.html#requirescontext-container. It is my understanding that I should be able to use the second example in a function chain, and pass the context once at the end (especially if I have multiple functions needing the same context). I made the change you suggested (except I used RequiresContextIOResultE), and mypy is happy, but the script doesn't run.

raceback (most recent call last):
  File "playground/test.py", line 43, in <module>
    get_ip_addr()("https://whatsmyip.ovh/json")
  File "playground/test.py", line 32, in get_ip_addr
    return RequiresContextIOResultE(inner)
  File "/usr/local/lib/python3.7/typing.py", line 677, in __call__
    result.__orig_class__ = self
  File "/usr/local/src/returns/returns/primitives/types.py", line 42, in __setattr__
    raise ImmutableStateError()
returns.primitives.exceptions.ImmutableStateError
dgjustice commented 4 years ago

For a big, ugly example... this works, but the typing is wrong. I am trying to clean it up, but I'm getting hung up on the simpler small steps ^^^. https://github.com/dgjustice/packtpub-downloader/blob/master/packt_downloader/__init__.py#L325-L369

sobolevn commented 4 years ago

Oh my!

Can you please post the script that raises an error? This does look like a bug in returns.

dgjustice commented 4 years ago

The script is linked in the first comment; all I changed was RequiresContext -> RequiresContextIOResultE. This brings up another point of confusion. What exactly is the difference between the two? To me, it looks like RequiresContextIOResultE tells a function to expect a context that is an IOResultE object. The typing signature only adds to the confusion. Is this correct? RequiresContext["dependency_type", "wrapped_function_return_type"]. The documentation is full of trivial examples with basic types (int, str, etc). I wouldn't mind contributing better examples, but I have to figure out how to make the library work first. :)

sobolevn commented 4 years ago

https://returns.readthedocs.io/en/latest/pages/context.html#why-cant-we-use-requirescontext-e-result-instead-of-requirescontextresult

dgjustice commented 4 years ago

That makes sense, thanks!

sobolevn commented 4 years ago

Glad to help, @dgjustice! 🤝