Skip to content

Commit db58b00

Browse files
authored
Renames .rescue to .lash (#671)
* Renames .rescue to .lash * Fixes docs * Fixes typesafety tests * Improves docs * Refs #674 * Refs #674 * Adds docs on how to create your own container * Fixes CI
1 parent 0c6aaff commit db58b00

File tree

80 files changed

+1301
-353
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+1301
-353
lines changed

CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ See [0Ver](https://0ver.org/).
1717
- **Breaking**: changes all `RequiresContext`-based type arguments order,
1818
previously we used to specify `_EnvType` as the first type argument,
1919
now it is the last one. This is done to respect new HKT rules
20+
- **Breaking**: renames `.rescue` to `.lash`
2021
- **Breaking**: removes all old interfaces from `primitives/interfaces.py`,
2122
use new typeclasses instead
2223
- **Breaking**: ``Maybe`` is fully reworked to be lawful
@@ -88,7 +89,9 @@ See [0Ver](https://0ver.org/).
8889
- Adds a lot of new typetests
8990
- Checks that now all math laws are checked for all types
9091
- Changes docs structure, adds new `Interfaces`, `HKT`, and `Methods` pages
91-
- Changed str function in BaseContainer class to repr function.
92+
- Changed `__str__` method in `BaseContainer` class to `__repr__` method
93+
- Adds `Quickstart` guide
94+
9295

9396
## 0.14.0
9497

docs/pages/contrib/pytest_plugins.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ It tests that containers do handle error track.
4444
def test_my_container(returns):
4545
assert not returns.is_error_handled(Failure(1))
4646
assert returns.is_error_handled(
47-
Failure(1).rescue(lambda _: Success('default value')),
47+
Failure(1).lash(lambda _: Success('default value')),
4848
)
4949
5050
We recommed to unit test big chunks of code this way.
@@ -53,7 +53,7 @@ you need at least one error handling at the very end.
5353

5454
This is how it works internally:
5555

56-
- Methods like ``fix`` and ``rescue`` mark errors
56+
- Methods like ``fix`` and ``lash`` mark errors
5757
inside the container as handled
5858
- Methods like ``map`` and ``alt`` just copies
5959
the error handling state from the old container to a new one,

docs/pages/create-your-own-container.rst

Lines changed: 160 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ What is a ``Pair``? Well, it is literally a pair of two values.
3030
No more, no less. Similar to a ``Tuple[FirstType, SecondType]``.
3131
But with extra goodies.
3232

33+
.. note::
34+
You can find all `code samples here <https://github.com/dry-python/returns/tree/master/tests/test_examples>`_.
35+
3336

3437
Step 1: Choosing right interfaces
3538
---------------------------------
@@ -52,19 +55,16 @@ And then subtype the interfaces that provide these methods and laws.
5255

5356
What interfaces a ``Pair`` type needs?
5457

55-
- :class:`returns.interfaces.equable.SupportsEquality`,
58+
- :class:`returns.interfaces.equable.Equable`,
5659
because two ``Pair`` instances can be compared
5760
- :class:`returns.interfaces.mappable.MappableN`,
5861
because the first type can be composed with pure functions
5962
- :class:`returns.interfaces.bindable.BindableN`,
6063
because a ``Pair`` can be bound to a function returning a new ``Pair``
6164
based on the first type
62-
- :class:`returns.interfaces.applicative.ApplicativeN`,
63-
because you can construct new ``Piar`` from a value
64-
and apply a wrapped function over it
6565
- :class:`returns.interfaces.altable.AltableN`,
6666
because the second type can be composed with pure functions
67-
- :class:`returns.interfaces.rescuable.RescuableN`,
67+
- :class:`returns.interfaces.lashable.LashableN`,
6868
because a ``Pair`` can be bound to a function returning a new ``Pair``
6969
based on the second type
7070

@@ -74,9 +74,8 @@ let's find pre-defined aliases we can reuse.
7474
Turns out, there are some of them!
7575

7676
- :class:`returns.interfaces.bimappable.BiMappableN`
77-
which combines ``MappableN`` and ``AltableN``
78-
- :class:`returns.interfaces.container.ContainerN` which combines
79-
``ApplicativeN`` and ``BindableN``
77+
which combines ``MappableN`` and ``AltableN``,
78+
it also requires to add a new method called ``.swap`` to change values order
8079

8180
Let's look at the resul:
8281

@@ -85,8 +84,8 @@ Let's look at the resul:
8584
>>> from typing_extensions import final
8685
>>> from typing import Callable, TypeVar, Tuple
8786
88-
>>> from returns.interfaces import container, bimappable, rescuable, equable
89-
>>> from returns.primitives.container import BaseContainer, container_equality
87+
>>> from returns.interfaces import bimappable, bindable, equable, lashable
88+
>>> from returns.primitives.container import BaseContainer
9089
>>> from returns.primitives.hkt import SupportsKind2
9190
9291
>>> _FirstType = TypeVar('_FirstType')
@@ -99,10 +98,10 @@ Let's look at the resul:
9998
... class Pair(
10099
... BaseContainer,
101100
... SupportsKind2['Pair', _FirstType, _SecondType],
102-
... container.Container2[_FirstType, _SecondType],
101+
... bindable.Bindable2[_FirstType, _SecondType],
103102
... bimappable.BiMappable2[_FirstType, _SecondType],
104-
... rescuable.Rescuable2[_FirstType, _SecondType],
105-
... equable.SupportsEquality,
103+
... lashable.Lashable2[_FirstType, _SecondType],
104+
... equable.Equable,
106105
... ):
107106
... def __init__(
108107
... self, inner_value: Tuple[_FirstType, _SecondType],
@@ -119,36 +118,171 @@ Let's look at the resul:
119118
Later we will talk about an actual implementation of all required methods.
120119

121120

122-
Step 2: Defining new interfaces and associated laws
123-
---------------------------------------------------
121+
Step 2: Initial implementation
122+
------------------------------
123+
124+
So, let's start writting some code!
125+
126+
We would need to implement all interface methods,
127+
otherwise ``mypy`` won't be happy.
128+
That's what it currently says on our type definition:
129+
130+
.. code::
131+
132+
error: Final class test_pair1.Pair has abstract attributes "alt", "bind", "equals", "lash", "map", "swap"
133+
134+
Looks like it already knows what methods should be there!
135+
136+
Ok, let's drop some initial and straight forward implementation.
137+
We will later make it more complex step by step.
138+
139+
.. literalinclude:: ../../tests/test_examples/test_pair1.py
140+
:linenos:
141+
142+
You can check our resulting source with ``mypy``. It would be happy this time.
143+
144+
145+
Step 3: New interfaces
146+
----------------------
147+
148+
As you can see our existing interfaces do not cover everything.
149+
We can potentially want several extra things:
124150

125-
After the initial analysis in the "Step 1",
126-
you can decide to introduce your own methods.
151+
1. A method that takes two arguments and returns a new ``Pair`` instance
152+
2. A named constructor to create a ``Pair`` from a single value
153+
3. A named constructor to create a ``Pair`` from two values
127154

128-
These methods can probably form an interface,
129-
if you want to make generic utilities for your type.
155+
We can define an interface just for this!
156+
It would be also nice to add all other interfaces there as supertypes.
130157

131-
Let's say your type will have ``.from_tuple`` and ``.replace`` methods,
132-
that can look like so:
158+
That's how it is going to look:
133159

160+
.. literalinclude:: ../../tests/test_examples/test_pair2.py
161+
:linenos:
162+
:pyobject: PairLikeN
134163

164+
Awesome! Now we have a new interface to implement. Let's do that!
135165

166+
.. literalinclude:: ../../tests/test_examples/test_pair2.py
167+
:linenos:
168+
:pyobject: Pair.pair
136169

137-
Step 3: Actual implementation
138-
-----------------------------
170+
.. literalinclude:: ../../tests/test_examples/test_pair2.py
171+
:linenos:
172+
:pyobject: Pair.from_unpaired
173+
174+
Looks like we are done!
139175

140176

141177
Step 4: Writting tests and docs
142178
-------------------------------
143179

180+
The best part about this type is that it is pure.
181+
So, we can write our tests inside docs!
144182

145-
Step 5: Writting type-tests
146-
---------------------------
183+
We are going to use
184+
`doctests <https://docs.python.org/3/library/doctest.html>`_
185+
builtin module for that.
186+
187+
This gives us several key benefits:
188+
189+
- All our docs has usage examples
190+
- All our examples are correct, because they are executed and tested
191+
- We don't need to write regular boring tests
192+
193+
Let's add docs and doctests! Let's use ``map`` method as a short example:
194+
195+
.. literalinclude:: ../../tests/test_examples/test_pair3.py
196+
:linenos:
197+
:pyobject: Pair.map
147198

199+
By adding these simple tests we would already have 100% coverage.
200+
But, what if we can completely skip writting tests, but still have 100%?
148201

149-
Step 6: Checking laws
202+
Let's discuss how we can achieve that with "Laws as values".
203+
204+
205+
Step 5: Checking laws
150206
---------------------
151207

208+
We already ship lots of laws with our interfaces.
209+
See our docs on :ref:`laws and checking them <hypothesis-plugins>`.
210+
211+
Moreover, you can also define your own laws!
212+
Let's add them to our ``PairLikeN`` interface.
213+
214+
Let's start with laws definition:
215+
216+
.. literalinclude:: ../../tests/test_examples/test_pair4.py
217+
:linenos:
218+
:pyobject: _LawSpec
219+
220+
And them let's add them to our ``PairLikeN`` interface:
221+
222+
.. literalinclude:: ../../tests/test_examples/test_pair4.py
223+
:linenos:
224+
:pyobject: PairLikeN
225+
:emphasize-lines: 9-12
226+
227+
The last to do is to call ``check_all_laws(Pair, use_init=True)``
228+
to generate 10 ``hypothesis`` test cases with hundreds real test cases inside.
229+
230+
Here's the final result of our brand new ``Pair`` type:
231+
232+
.. literalinclude:: ../../tests/test_examples/test_pair4.py
233+
:linenos:
234+
235+
236+
Step 6: Writting type-tests
237+
---------------------------
238+
239+
.. note::
240+
You can find all `type-tests here <https://github.com/dry-python/returns/tree/master/typesafety/test_examples>`_.
241+
242+
The next thing we want is to write a type-test!
243+
244+
What is a type-test? This is a special type of tests for your typing.
245+
We run ``mypy`` on top of tests and use snapshots to assert the result.
246+
247+
We recommend to use `pytest-mypy-plugins <https://github.com/typeddjango/pytest-mypy-plugins>`_.
248+
`Read more <https://sobolevn.me/2019/08/testing-mypy-types>`_
249+
about how to use it.
250+
251+
Let's start with a simple test
252+
to make sure our ``.pair`` function works correctly:
253+
254+
.. warning::
255+
Please, don't use ``env:`` property the way we do here.
256+
We need it since we store our example in ``tests/`` folder.
257+
And we have to tell ``mypy`` how to find it.
258+
259+
.. literalinclude:: ../../typesafety/test_examples/test_pair4_def.yml
260+
:linenos:
261+
262+
Ok, now, let's try to raise an error by using it incorrectly:
263+
264+
.. literalinclude:: ../../typesafety/test_examples/test_pair4_error.yml
265+
:linenos:
266+
152267

153268
Step 7: Reusing code
154269
--------------------
270+
271+
The last (but not the least!) thing you need
272+
to know is that you can reuse all code
273+
we already have for this new ``Pair`` type.
274+
275+
This is because of our :ref:`hkt` feature.
276+
277+
So, let's say we want to use native :func:`~returns.pointfree.map.map_`
278+
pointfree function with our new ``Pair`` type.
279+
Let's test that it will work correctly:
280+
281+
.. literalinclude:: ../../typesafety/test_examples/test_pair4_reuse.yml
282+
:linenos:
283+
284+
Yes, it works!
285+
286+
Now you have fully working, typed, documented, lawful, and tested primitive.
287+
You can build any other primitive
288+
you need for your business logic or infrastructure.

docs/pages/interfaces.rst

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,9 @@ We follow a very specific naming convention in our interface names.
6262

6363
If interface does not depend on the number of types
6464
it works with and is always the same, we name it as is.
65-
For example, ``SupportsEquality`` is always the same
65+
For example, ``Equable`` is always the same
6666
and does not depend on the number of type arguments.
67+
We use adjectives to name these interfaces.
6768

6869
Secondly, if interface depends on the number of type arguments,
6970
it is named with ``N`` suffix in the end.
@@ -81,7 +82,7 @@ Laws
8182
~~~~
8283

8384
Some interfaces define its laws as values.
84-
These laws can be viewed as test that are attached to an interface.
85+
These laws can be viewed as tests that are attached to the specific interface.
8586

8687
We are able to check them of any type
8788
that implements a given interfaces with laws
@@ -447,7 +448,7 @@ Here's a full overview of all our interfaces:
447448
returns.interfaces.mappable
448449
returns.interfaces.bindable
449450
returns.interfaces.applicative
450-
returns.interfaces.rescuable
451+
returns.interfaces.lashable
451452
returns.interfaces.altable
452453
returns.interfaces.bimappable
453454
returns.interfaces.unwrappable
@@ -467,8 +468,8 @@ Here's a full overview of all our interfaces:
467468

468469
Let's review it one by one.
469470

470-
SupportsEquality
471-
~~~~~~~~~~~~~~~~
471+
Equable
472+
~~~~~~~
472473

473474
.. autoclasstree:: returns.interfaces.equable
474475
:strict:
@@ -527,13 +528,13 @@ BiMappable
527528
:members:
528529
:private-members:
529530

530-
Rescuable
531-
~~~~~~~~~
531+
Lashable
532+
~~~~~~~~
532533

533-
.. autoclasstree:: returns.interfaces.rescuable
534+
.. autoclasstree:: returns.interfaces.lashable
534535
:strict:
535536

536-
.. automodule:: returns.interfaces.rescuable
537+
.. automodule:: returns.interfaces.lashable
537538
:members:
538539
:private-members:
539540

docs/pages/pointfree.rst

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -119,27 +119,27 @@ We also have a long list of other ``bind_*`` functions, like:
119119
- ``bind_awaitable`` to bind async non-container functions
120120

121121

122-
rescue
123-
------
122+
lash
123+
----
124124

125-
Pointfree ``rescue()`` function is an alternative
126-
to ``.rescue()`` container method.
125+
Pointfree ``lash()`` function is an alternative
126+
to ``.lash()`` container method.
127127
It is also required for better declarative programming.
128128

129129
.. code:: python
130130
131-
>>> from returns.pointfree import rescue
131+
>>> from returns.pointfree import lash
132132
>>> from returns.result import Success, Failure, Result
133133
134134
>>> def function(arg: str) -> Result[int, str]:
135135
... return Success(1)
136136
137137
>>> container: Result[int, str] = Failure('a')
138138
>>> # We now have two way of composining these entities.
139-
>>> # 1. Via ``.rescue``:
140-
>>> assert container.rescue(function) == Success(1)
141-
>>> # 2. Or via ``rescue`` function, the same but in the inverse way:
142-
>>> assert rescue(function)(container) == Success(1)
139+
>>> # 1. Via ``.lash``:
140+
>>> assert container.lash(function) == Success(1)
141+
>>> # 2. Or via ``lash`` function, the same but in the inverse way:
142+
>>> assert lash(function)(container) == Success(1)
143143
144144
145145
apply
@@ -318,7 +318,7 @@ API Reference
318318

319319
.. autofunction:: returns.pointfree.cond
320320

321-
.. autofunction:: returns.pointfree.rescue
321+
.. autofunction:: returns.pointfree.lash
322322

323323
.. autofunction:: returns.pointfree.unify
324324

0 commit comments

Comments
 (0)