Skip to content

WIP: adds hkt support #446

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Jul 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a8b98dd
WIP: adds hkt support for Functor and Maybe
sobolevn Jul 4, 2020
0d37d24
Adds hkt support for IO
sobolevn Jul 4, 2020
24e3a24
Adds variardic Kind, adds Functor to Future and Result types
sobolevn Jul 4, 2020
0c8f649
Fixes docs
sobolevn Jul 4, 2020
353b80d
Fixes CI
sobolevn Jul 4, 2020
81e8dc0
Adds changelog
sobolevn Jul 4, 2020
ec88e8e
Drops python3.6
sobolevn Jul 4, 2020
6b959cd
Adds dekind and Applicative support
sobolevn Jul 5, 2020
44a11d4
Adds Monad typeclass
sobolevn Jul 5, 2020
dd0d57c
Fixes CI
sobolevn Jul 5, 2020
8a29ab0
Changes Reader-based type args order
sobolevn Jul 5, 2020
08c670e
Fixes examples and docs for RequiresContext
sobolevn Jul 5, 2020
f8e5bc1
Adds correct Kind + Generic usage
sobolevn Jul 5, 2020
8647a5d
Fully working and tested implementation
sobolevn Jul 10, 2020
d60e41a
Returns python3.6 back in
sobolevn Jul 10, 2020
29cfb66
Fixes linting
sobolevn Jul 10, 2020
79f317d
Fixes context typesafety tests
sobolevn Jul 10, 2020
d0a093a
Adds tests for KindN
sobolevn Jul 10, 2020
e5da354
Merge master
sobolevn Jul 10, 2020
fe7b8de
Adds map_ method and pointfree
sobolevn Jul 10, 2020
9415f78
Fixes more type tests
sobolevn Jul 10, 2020
60b40c4
Fixes some more tests
sobolevn Jul 10, 2020
e0b01da
Changes how .unwrap works, adds Unwrappable interface
sobolevn Jul 11, 2020
d6e357a
Adds apply pointfree and method functions
sobolevn Jul 11, 2020
4d32594
Updates docs
sobolevn Jul 11, 2020
c0b5b4b
Fixes linting
sobolevn Jul 11, 2020
63ea690
Adds result-based and rescue
sobolevn Jul 11, 2020
5f5913f
Fixes CI
sobolevn Jul 12, 2020
dcab55d
Fixes CI
sobolevn Jul 12, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,46 @@ incremental in minor, bugfixes only are patches.
See [0Ver](https://0ver.org/).


## 0.15.0 WIP

### Features

- Adds Higher Kinded Types partial support
- **Breaking**: makes our `mypy` plugin not optional, but required!
- **Breaking**: changes all `RequiresContext`-based type arguments order,
previously we used to specify `_EnvType` as the first type argument,
now it is the last one. This is done to respect new HKT rules
- **Breaking**: removes all old interfaces from `primitives/interfaces.py`,
use new typeclasses instead
- **Breaking**: removes `value_or` pointfree method,
because it is impossible to express with HKT
- **Breaking**: removes `.value_or`, `.unwrap`, and `.failure` methods
from `FutureResult` and `RequiresContext` based types,
because we do require these methods to raise an exception on failure,
but these methods were lazy and did not raise the required exception
- **Breaking**: changes how `is_successful` is typed:
now we allow any `Unwrappable` interface instances there,
including custom ones
- **Breaking**: changes `UnwrapFailedError` constructor,
now it does accept an `Unwrappable` instance instead of a `BaseContainer`
- Adds new public interfaces: see `returns.interfaces`
- Adds `methods` package with all the composition methods we support
- Adds `collect_trace` helper function for better development experience
- Adds missing `from_requires_context_future_result` to `RequiresContext`
- Adds `@abstractmethod` to abstract methods in abstract containers
like `Result`, `Maybe`, and `IOResult`
- Adds `__slots__` to `UnwrapFailedError` with `halted_container`

### Bugfixes

- **Breaking**: fixes serious typing issue and changes how `flow` works,
now it has a hard limit of 21 parameters: 1 instance + 20 functions

### Misc

- Adds a lot of new typetests


## 0.14.0

### Features
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Make your functions return something meaningful, typed, and safe!
pip install returns
```

You might also want to [configure](https://returns.readthedocs.io/en/latest/pages/container.html#type-safety)
You are also required to [configure](https://returns.readthedocs.io/en/latest/pages/container.html#type-safety)
`mypy` correctly and install our plugin
to fix [this existing issue](https://github.com/python/mypy/issues/3157):

Expand Down Expand Up @@ -123,7 +123,7 @@ Much better, isn't it?

## RequiresContext container

Many developers do use some kind of [dependency injection](https://github.com/dry-python/dependencies) in Python.
Many developers do use some kind of dependency injection in Python.
And usually it is based on the idea
that there's some kind of a container and assembly process.

Expand Down Expand Up @@ -193,11 +193,11 @@ from returns.context import RequiresContext
class _Deps(Protocol): # we rely on abstractions, not direct values or types
WORD_THRESHOLD: int

def calculate_points(word: str) -> RequiresContext[_Deps, int]:
def calculate_points(word: str) -> RequiresContext[int, _Deps]:
guessed_letters_count = len([letter for letter in word if letter != '.'])
return _award_points_for_letters(guessed_letters_count)

def _award_points_for_letters(guessed: int) -> RequiresContext[_Deps, int]:
def _award_points_for_letters(guessed: int) -> RequiresContext[int, _Deps]:
return RequiresContext(
lambda deps: 0 if guessed < deps.WORD_THRESHOLD else guessed,
)
Expand All @@ -208,7 +208,7 @@ And have the type-safety to check what you pass to cover your back.
Check out [RequiresContext](https://returns.readthedocs.io/en/latest/pages/context.html) docs for more. There you will learn how to make `'.'` also configurable.

We also have [RequiresContextResult](https://returns.readthedocs.io/en/latest/pages/context.html#requirescontextresult-container)
for context-related operations that might fail.
for context-related operations that might fail. And also [RequiresContextIOResult](https://returns.readthedocs.io/en/latest/pages/context.html#requirescontextioresult-container) and [RequiresContextFutureResult](https://returns.readthedocs.io/en/latest/pages/context.html#requirescontextfutureresult-container).


## Result container
Expand Down
3 changes: 3 additions & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Contents

pages/container.rst
pages/railway.rst
pages/hkt.rst
pages/interfaces.rst

.. toctree::
:maxdepth: 2
Expand All @@ -35,6 +37,7 @@ Contents

pages/pipeline.rst
pages/converters.rst
pages/methods.rst
pages/pointfree.rst
pages/functions.rst
pages/curry.rst
Expand Down
12 changes: 2 additions & 10 deletions docs/pages/container.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ The difference is simple:
- ``map`` works with functions that return regular value
- ``bind`` works with functions that return new container of the same type

:func:`~returns.primitives.container.Bindable.bind`
:func:`returns.primitives.container.Bindable.bind`
is used to literally bind two different containers together.

.. code:: python
Expand All @@ -83,7 +83,7 @@ is used to literally bind two different containers together.
# Can be assumed as either Success[float] or Failure[str]:
result: Result[float, str] = value.bind(may_fail)

And we have :func:`~returns.primitives.container.Mappable.map`
And we have :func:`returns.interfaces.mappable.Mappable.map`
to use containers with regular functions.

.. code:: python
Expand Down Expand Up @@ -335,11 +335,3 @@ It defines some basic things like representation, hashing, pickling, etc.
.. automodule:: returns.primitives.container
:members:
:special-members:

Here are our interfaces (or protocols to be more specific)
that we use inside our app:

.. autoclasstree:: returns.primitives.interfaces

.. automodule:: returns.primitives.interfaces
:members:
44 changes: 22 additions & 22 deletions docs/pages/context.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Context
=======

`Dependency injection <https://github.com/dry-python/dependencies>`_ is a popular software architechture pattern.
Dependency injection is a popular software architechture pattern.

It's main idea is that you provide `Inversion of Control <https://en.wikipedia.org/wiki/Inversion_of_control>`_
and can pass different things into your logic instead of hardcoding you stuff.
Expand Down Expand Up @@ -125,11 +125,11 @@ Let's see how our code changes:
class _Deps(Protocol): # we rely on abstractions, not direct values or types
WORD_THRESHOLD: int

def calculate_points(word: str) -> RequiresContext[_Deps, int]:
def calculate_points(word: str) -> RequiresContext[int, _Deps]:
guessed_letters_count = len([letter for letter in word if letter != '.'])
return _award_points_for_letters(guessed_letters_count)

def _award_points_for_letters(guessed: int) -> RequiresContext[_Deps, int]:
def _award_points_for_letters(guessed: int) -> RequiresContext[int, _Deps]:
return RequiresContext(
lambda deps: 0 if guessed < deps.WORD_THRESHOLD else guessed,
)
Expand All @@ -149,7 +149,7 @@ How can we do that with our existing function?

.. code:: python

def calculate_points(word: str) -> RequiresContext[_Deps, int]:
def calculate_points(word: str) -> RequiresContext[int, _Deps]:
guessed_letters_count = len([letter for letter in word if letter != '.'])
return _award_points_for_letters(guessed_letters_count)

Expand All @@ -173,8 +173,8 @@ Let's see the final result:
WORD_THRESHOLD: int
UNGUESSED_CHAR: str

def calculate_points(word: str) -> RequiresContext[_Deps, int]:
def factory(deps: _Deps) -> RequiresContext[_Deps, int]:
def calculate_points(word: str) -> RequiresContext[int, _Deps]:
def factory(deps: _Deps) -> RequiresContext[int, _Deps]:
guessed_letters_count = len([
letter for letter in word if letter != deps.UNGUESSED_CHAR
])
Expand Down Expand Up @@ -203,7 +203,7 @@ It can be illustrated as a simple nested function:
... def inner(deps: str) -> bool:
... return len(deps) > limit
... return inner
...

>>> assert first(2)('abc') # first(limit)(deps)
>>> assert not first(5)('abc') # first(limit)(deps)

Expand All @@ -225,7 +225,7 @@ We can wrap it in ``RequiresContext`` container to allow better composition!

>>> from returns.context import RequiresContext

>>> def first(limit: int) -> RequiresContext[str, bool]:
>>> def first(limit: int) -> RequiresContext[bool, str]:
... def inner(deps: str) -> bool:
... return len(deps) > limit
... return RequiresContext(inner) # wrapping function here!
Expand All @@ -252,7 +252,7 @@ And then normal logical execution happens.
RequiresContextResult container
-------------------------------

This container is a combintaion of ``RequiresContext[env, Result[a, b]]``.
This container is a combintaion of ``RequiresContext[Result[a, b], env]``.
Which means that it is a wrapper around pure function that might fail.

We also added a lot of useful methods for this container,
Expand All @@ -272,7 +272,7 @@ Use it when you work with pure context-related functions that might fail.
RequiresContextIOResult container
---------------------------------

This container is a combintaion of ``RequiresContext[env, IOResult[a, b]]``.
This container is a combintaion of ``RequiresContext[IOResult[a, b], env]``.
Which means that it is a wrapper around impure function that might fail.

We also added a lot of useful methods for this container,
Expand Down Expand Up @@ -301,7 +301,7 @@ This is basically **the main type** that is going to be used in most apps.
RequiresContextFutureResult container
-------------------------------------

This container is a combintaion of ``RequiresContext[env, FutureResult[a, b]]``.
This container is a combintaion of ``RequiresContext[FutureResult[a, b], env]``.
Which means that it is a wrapper around impure async function that might fail.

Here's how it should be used:
Expand Down Expand Up @@ -334,7 +334,7 @@ Here's how it should be used:

def _fetch_post(
post_id: int,
) -> RequiresContextFutureResultE[httpx.AsyncClient, _Post]:
) -> RequiresContextFutureResultE[_Post, httpx.AsyncClient]:
return ContextFutureResult[httpx.AsyncClient].ask().bind_future_result(
lambda client: future_safe(client.get)(_URL.format(post_id)),
).bind_result(
Expand All @@ -345,7 +345,7 @@ Here's how it should be used:

def show_titles(
number_of_posts: int,
) -> RequiresContextFutureResultE[httpx.AsyncClient, Sequence[_TitleOnly]]:
) -> RequiresContextFutureResultE[Sequence[_TitleOnly], httpx.AsyncClient]:
return RequiresContextFutureResultE.from_iterable([
# Notice how easily we compose async and sync functions:
_fetch_post(post_id).map(lambda post: post['title'])
Expand Down Expand Up @@ -549,7 +549,7 @@ We can model the similar idea with ``RequiresContext``:

>>> from returns.context import RequiresContext

>>> def my_function(first: int, second: int) -> RequiresContext[bool, int]:
>>> def my_function(first: int, second: int) -> RequiresContext[int, bool]:
... def factory(print_result: bool) -> int:
... original = first + second
... if print_result:
Expand All @@ -566,21 +566,21 @@ it is easier to change the behaviour of a function with ``RequiresContext``.
While decorator with arguments glues values to a function forever.
Decide when you need which behaviour carefully.

Why can’t we use RequiresContext[e, Result] instead of RequiresContextResult?
Why can’t we use RequiresContext[Result, e] instead of RequiresContextResult?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

We actually can! But, it is harder to write.
And ``RequiresContextResult`` is actually
the very same thing as ``RequiresContext[e, Result]``, but has nicer API:
the very same thing as ``RequiresContext[Result, e]``, but has nicer API:

.. code:: python

x: RequiresContext[int, Result[int, str]]
x: RequiresContext[Result[int, str], int]
x.map(lambda result: result.map(lambda number: number + 1))

# Is the same as:

y: RequiresContextResult[int, int, str]
y: RequiresContextResult[int, str, int]
y.map(lambda number: number + 1)

The second one looks better, doesn't it?
Expand All @@ -599,8 +599,8 @@ So, using this technique is better:

from returns.context import Context, RequiresContext

def some_context(*args, **kwargs) -> RequiresContext[int, str]:
def factory(deps: int) -> RequiresContext[int, str]:
def some_context(*args, **kwargs) -> RequiresContext[str, int]:
def factory(deps: int) -> RequiresContext[str, int]:
...
return Context[int].ask().bind(factory)

Expand All @@ -615,7 +615,7 @@ that do pretty much the same as ``RequiresContext`` container.
What is the difference? Why do we need each of them?

Let's find out!
Tools like `dependencies <https://github.com/dry-python/dependencies>`_
Tools like `dependencies <https://github.com/proofit404/dependencies>`_
or `punq <https://github.com/bobthemighty/punq>`_
tries to:

Expand All @@ -635,7 +635,7 @@ All it does is: provides simple API to compose functions
that need additional context (or dependencies) to run.

You can even use them together: ``RequiresContext`` will pass depedencies
built by ``dry-python/dependencies`` (or any other tool of your choice)
built by ``punq`` (or any other tool of your choice)
as a ``deps`` parameter to ``RequiresContext`` instance.

When to use which? Let's dig into it!
Expand Down
34 changes: 23 additions & 11 deletions docs/pages/contrib/mypy_plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,15 @@ This will allow to keep them in sync with the upstream.
Supported features
------------------

- ``kind`` feature adds Higher Kinded Types (HKT) support
- ``curry`` feature allows to write typed curried functions
- ``partial`` feature allows to write typed partial application
- ``flow`` feature allows to write better typed functional pipelines
with ``flow`` function
- ``pipe`` feature allows to write better typed functional pipelines
with ``pipe`` function
- ``decorators`` allows to infer types of functions that are decorated
with ``@safe``, ``@maybe``, ``@impure``, etc
- ``pointfree`` provides better typing inference
for some problematic :ref:`pointfree` helpers


Further reading
Expand All @@ -71,11 +73,22 @@ API Reference
Plugin defenition
~~~~~~~~~~~~~~~~~

.. automodule:: returns.contrib.mypy._consts
:members:

.. autoclasstree:: returns.contrib.mypy.returns_plugin

.. automodule:: returns.contrib.mypy.returns_plugin
:members:

Kind
~~~~

.. autoclasstree:: returns.contrib.mypy._features.kind

.. automodule:: returns.contrib.mypy._features.kind
:members:

Curry
~~~~~

Expand All @@ -100,19 +113,18 @@ Flow
.. automodule:: returns.contrib.mypy._features.flow
:members:

Decorators
~~~~~~~~~~
Pipe
~~~~

.. autoclasstree:: returns.contrib.mypy._features.decorators
.. autoclasstree:: returns.contrib.mypy._features.pipe

.. automodule:: returns.contrib.mypy._features.decorators
.. automodule:: returns.contrib.mypy._features.pipe
:members:

Decorators
~~~~~~~~~~

Pointfree
~~~~~~~~~

.. autoclasstree:: returns.contrib.mypy._features.pointfree
.. autoclasstree:: returns.contrib.mypy._features.decorators

.. automodule:: returns.contrib.mypy._features.pointfree
.. automodule:: returns.contrib.mypy._features.decorators
:members:
10 changes: 10 additions & 0 deletions docs/pages/hkt.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.. _hkt:

Higher Kinded Types
===================

API Reference
-------------

.. automodule:: returns.primitives.hkt
:members:
Loading