Skip to content

Commit 79bae74

Browse files
authored
Adds interfaces docs (#692)
1 parent 02f3737 commit 79bae74

File tree

2 files changed

+278
-53
lines changed

2 files changed

+278
-53
lines changed

docs/pages/interfaces.rst

Lines changed: 277 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,12 @@ that implements the ``Mappable`` interface:
112112
>>> maybe_str: Maybe[str] = Some('example')
113113
>>> assert maybe_str.map(can_be_mapped) == Some('example!')
114114
115-
:class:`~returns.interfaces.mappable.MappableN` interface help us to
115+
:class:`~MappableN` interface helps us to
116116
create our own mappable container like :class:`~returns.maybe.Maybe`.
117117

118118
.. code:: python
119119
120-
>>> from typing import Any, Callable, TypeVar
120+
>>> from typing import Callable, TypeVar
121121
122122
>>> from returns.interfaces.mappable import Mappable1
123123
>>> from returns.primitives.hkt import SupportsKind1
@@ -154,12 +154,12 @@ With our ``Number`` mappable class we can compose easily math functions with it.
154154
Laws
155155
~~~~
156156

157-
To make sure your mappable implementation is right, you can apply the
158-
Mappable laws on it to test.
157+
To make sure your ``Mappable`` implementation is right,
158+
you can apply the ``Mappable`` laws on it to test.
159159

160160
1. :func:`Identity Law <_LawSpec.identity_law>`:
161-
When we pass the identity function to the map method,
162-
the mappable has to be the same, unaltered.
161+
When we pass the identity function to the ``map`` method,
162+
the ``Mappable`` instance has to be the same, unchanged.
163163

164164
.. code:: python
165165
@@ -201,44 +201,282 @@ Bindable
201201

202202
Bindable is something that we can bind with a function. Like
203203
:class:`~returns.maybe.Maybe`, so
204-
:class:`~returns.interfaces.bindable.BindableN` interface will help
204+
:class:`~BindableN` interface will help
205205
us to create our custom bindable.
206206

207207
.. code:: python
208208
209-
>>> from dataclasses import dataclass
210-
>>> from typing import Any, Callable, TypeVar
209+
>>> from typing import Callable, TypeVar
211210
212211
>>> from returns.interfaces.bindable import Bindable1
213-
>>> from returns.primitives.hkt import SupportsKind1
212+
>>> from returns.primitives.hkt import SupportsKind1, Kind1, dekind
214213
>>> from returns.primitives.container import BaseContainer
215214
216-
>>> _BagContentType = TypeVar('_BagContentType')
217-
>>> _NewBagContentType = TypeVar('_NewBagContentType')
215+
>>> _NumberType = TypeVar('_NumberType')
216+
>>> _NewNumberType = TypeVar('_NewNumberType')
218217
219-
>>> class Bag(
218+
>>> class Number(
220219
... BaseContainer,
221-
... SupportsKind1['Bag', int],
222-
... Bindable1[_BagContentType],
220+
... SupportsKind1['Number', _NumberType],
221+
... Bindable1[_NumberType],
223222
... ):
224-
... def __init__(self, inner_value: _BagContentType) -> None:
223+
... def __init__(self, inner_value: _NumberType) -> None:
225224
... super().__init__(inner_value)
226225
...
227-
... def bind(
226+
... def bind( # This method is required by Bindable
228227
... self,
229-
... function: Callable[[_BagContentType], 'Bag[_NewBagContentType]']
230-
... ) -> 'Bag[_NewBagContentType]':
231-
... return function(self._inner_value)
228+
... function: Kind1[
229+
... 'Number',
230+
... Callable[[_NumberType], 'Number[_NewNumberType]'],
231+
... ],
232+
... ) -> 'Number[_NewNumberType]':
233+
... return dekind(function(self._inner_value))
234+
235+
And here's how we can use it:
236+
237+
.. code:: python
238+
239+
>>> def double(arg: int) -> Number[int]:
240+
... return Number(arg * 2)
241+
242+
>>> number = Number(5)
243+
>>> assert number.bind(double) == Number(10)
244+
245+
246+
Applicative
247+
-----------
248+
249+
.. currentmodule:: returns.interfaces.applicative
250+
251+
Something is considered applicative
252+
if it is a functor already and,
253+
moreover, we can ``apply`` another container to it
254+
and construct a new value with ``.from_value`` method.
232255

233-
>>> @dataclass
234-
... class Peanuts(object):
235-
... quantity: int
256+
An example in this library is :class:`~returns.maybe.Maybe`,
257+
that implements the ``Mappable`` and ``Applicative`` interfaces:
258+
259+
.. code:: python
260+
261+
>>> from returns.maybe import Maybe, Some
236262
237-
>>> def get_half(peanuts: Peanuts) -> Bag[Peanuts]:
238-
... return Bag(Peanuts(peanuts.quantity // 2))
263+
>>> maybe_str = Maybe.from_value('example')
264+
>>> maybe_func = Maybe.from_value(len) # we use function as a value!
239265
240-
>>> bag_of_peanuts: Bag[Peanuts] = Bag(Peanuts(10))
241-
>>> assert bag_of_peanuts.bind(get_half) == Bag(Peanuts(5))
266+
>>> assert maybe_str.apply(maybe_func) == Some(7)
267+
268+
As you see, ``apply`` takes a container with a function inside
269+
and applies it to the currect value inside the container.
270+
271+
This way we really execute ``Maybe.from_value(len('example'))``.
272+
273+
:class:`~ApplicativeN` which is a subtype of
274+
:class:`~returns.interfaces.mappable.MappableN` interface helps us to
275+
create our own applicative container like :class:`~returns.maybe.Maybe`.
276+
277+
.. code:: python
278+
279+
>>> from typing import Callable, TypeVar
280+
281+
>>> from returns.interfaces.applicative import Applicative1
282+
>>> from returns.primitives.hkt import SupportsKind1, Kind1, dekind
283+
>>> from returns.primitives.container import BaseContainer
284+
285+
>>> _NumberType = TypeVar('_NumberType')
286+
>>> _NewNumberType = TypeVar('_NewNumberType')
287+
288+
>>> class Number(
289+
... BaseContainer,
290+
... SupportsKind1['Number', _NumberType],
291+
... Applicative1[_NumberType],
292+
... ):
293+
... def __init__(self, inner_value: _NumberType) -> None:
294+
... super().__init__(inner_value)
295+
...
296+
... def map( # This method is required by Mappable
297+
... self,
298+
... function: Callable[[_NumberType], _NewNumberType]
299+
... ) -> 'Number[_NewNumberType]':
300+
... return Number(function(self._inner_value))
301+
...
302+
... def apply( # This method is required by Applicative
303+
... self,
304+
... container: Kind1[
305+
... 'Number',
306+
... Callable[[_NumberType], _NewNumberType],
307+
... ],
308+
... ) -> 'Number[_NewNumberType]':
309+
... return Number.from_value(
310+
... dekind(container._inner_value(self._inner_value)),
311+
... )
312+
...
313+
... @classmethod
314+
... def from_value( # This method is required by Applicative
315+
... cls,
316+
... inner_value: _NewNumberType,
317+
... ) -> 'Number[_NewNumberType]':
318+
... return Number(inner_value)
319+
320+
With our ``Number`` mappable class we can compose easily math functions with it.
321+
322+
.. code:: python
323+
324+
>>> def my_math_function(number: int) -> int:
325+
... return number - 1
326+
327+
>>> number = Number(3)
328+
>>> number_function = Number.from_value(my_math_function)
329+
330+
>>> assert number.apply(number_function) == Number(2)
331+
332+
Laws
333+
~~~~
334+
335+
To make sure your ``Applicative`` implementation is right,
336+
you can apply the ``Applicative`` laws on it to test.
337+
338+
1. :func:`Identity Law <_LawSpec.identity_law>`:
339+
When we pass an applicative instance
340+
with wrapped identity function to the ``apply`` method,
341+
the ``Applicative`` has to be the same, unchanged.
342+
343+
.. code:: python
344+
345+
>>> from returns.functions import identity
346+
347+
>>> applicative_number: Number[int] = Number(1)
348+
>>> assert applicative_number.apply(
349+
... applicative_number.from_value(identity),
350+
... ) == Number(1)
351+
352+
2. :func:`Interchange Law <_LawSpec.interchange_law>`:
353+
We can start our composition with both raw value and a function.
354+
355+
.. code:: python
356+
357+
>>> def function(arg: int) -> int:
358+
... return arg + 1
359+
360+
>>> raw_value = 5
361+
362+
>>> assert Number.from_value(raw_value).apply(
363+
... Number.from_value(function),
364+
... ) == Number.from_value(function).apply(
365+
... Number.from_value(lambda inner: inner(raw_value)),
366+
... )
367+
368+
3. :func:`Homomorphism Law <_LawSpec.homomorphism_law>`:
369+
The homomorphism law says that
370+
applying a wrapped function to a wrapped value is the same
371+
as applying the function to the value in the normal way
372+
and then using ``.from_value`` on the result.
373+
374+
.. code:: python
375+
376+
>>> def function(arg: int) -> int:
377+
... return arg + 1
378+
379+
>>> raw_value = 5
380+
381+
>>> assert Number.from_value(
382+
... function(raw_value),
383+
... ) == Number.from_value(raw_value).apply(
384+
... Number.from_value(function),
385+
... )
386+
387+
4. :func:`Composition Law <_LawSpec.composition_law>`:
388+
Appling two functions twice is the same
389+
as applying their composition once.
390+
391+
.. code:: python
392+
393+
>>> from returns.functions import compose
394+
395+
>>> def first(arg: int) -> int:
396+
... return arg * 2
397+
398+
>>> def second(arg: int) -> int:
399+
... return arg + 1
400+
401+
>>> instance = Number(5)
402+
>>> assert instance.apply(
403+
... Number.from_value(compose(first, second)),
404+
... ) == instance.apply(
405+
... Number.from_value(first),
406+
... ).apply(
407+
... Number.from_value(second),
408+
... )
409+
410+
Plus all laws from ``MappableN`` interface.
411+
412+
413+
Container
414+
---------
415+
416+
.. currentmodule:: returns.interfaces.container
417+
418+
:class:`~ContainerN` is a central piece of our library.
419+
It is an interface that combines
420+
:class:`~returns.interfaces.applicative.ApplicativeN`
421+
and
422+
:class:`~returns.interfaces.bindable.BindableN` together.
423+
424+
So, in other words: ``Container`` is an ``Apllicative`` that you can ``bind``!
425+
426+
.. code:: python
427+
428+
>>> from typing import Callable, TypeVar
429+
430+
>>> from returns.interfaces.container import Container1
431+
>>> from returns.primitives.hkt import SupportsKind1, Kind1, dekind
432+
>>> from returns.primitives.container import BaseContainer
433+
434+
>>> _NumberType = TypeVar('_NumberType')
435+
>>> _NewNumberType = TypeVar('_NewNumberType')
436+
437+
>>> class Number(
438+
... BaseContainer,
439+
... SupportsKind1['Number', _NumberType],
440+
... Container1[_NumberType],
441+
... ):
442+
... def __init__(self, inner_value: _NumberType) -> None:
443+
... super().__init__(inner_value)
444+
...
445+
... def map( # This method is required by Mappable
446+
... self,
447+
... function: Callable[[_NumberType], _NewNumberType]
448+
... ) -> 'Number[_NewNumberType]':
449+
... return Number(function(self._inner_value))
450+
...
451+
... def bind( # This method is required by Bindable
452+
... self,
453+
... function: Kind1[
454+
... 'Number',
455+
... Callable[[_NumberType], 'Number[_NewNumberType]'],
456+
... ],
457+
... ) -> 'Number[_NewNumberType]':
458+
... return dekind(function(self._inner_value))
459+
...
460+
... def apply( # This method is required by Applicative
461+
... self,
462+
... container: Kind1[
463+
... 'Number',
464+
... Callable[[_NumberType], _NewNumberType],
465+
... ],
466+
... ) -> 'Number[_NewNumberType]':
467+
... return Number.from_value(
468+
... container._inner_value(self._inner_value),
469+
... )
470+
...
471+
... @classmethod
472+
... def from_value( # This method is required by Applicative
473+
... cls,
474+
... inner_value: _NewNumberType,
475+
... ) -> 'Number[_NewNumberType]':
476+
... return Number(inner_value)
477+
478+
This code gives us an opportunity to use ``Number``
479+
with ``map``, ``apply``, and ``bind`` as we already did in the examples above.
242480

243481
Laws
244482
~~~~
@@ -252,19 +490,19 @@ respect three new laws.
252490

253491
.. code:: python
254492
255-
>>> def can_be_bound(value: int) -> Bag[Peanuts]:
256-
... return Bag(Peanuts(value))
493+
>>> def can_be_bound(value: int) -> Number[int]:
494+
... return Number(value)
257495
258-
>>> assert Bag(5).bind(can_be_bound) == can_be_bound(5)
496+
>>> assert Number.from_value(5).bind(can_be_bound) == can_be_bound(5)
259497
260498
2. :func:`Right Identity <_LawSpec.right_identity_law>`:
261499
If we pass the bindable constructor through ``bind`` must
262500
have to be the same result as instantiating the bindable on our own.
263501

264502
.. code:: python
265503
266-
>>> bag = Bag(Peanuts(2))
267-
>>> assert bag.bind(Bag) == Bag(Peanuts(2))
504+
>>> number = Number(2)
505+
>>> assert number.bind(Number) == Number(2)
268506
269507
3. :func:`Associative Law <_LawSpec.associative_law>`:
270508
Given two functions, ``x`` and ``y``, calling the bind
@@ -275,31 +513,18 @@ respect three new laws.
275513

276514
.. code:: python
277515
278-
>>> def minus_one(peanuts: Peanuts) -> Bag[Peanuts]:
279-
... return Bag(Peanuts(peanuts.quantity - 1))
516+
>>> def minus_one(arg: int) -> Number[int]:
517+
... return Number(arg - 1)
280518
281-
>>> def half(peanuts: Peanuts) -> Bag[Peanuts]:
282-
... return Bag(Peanuts(peanuts.quantity // 2))
519+
>>> def half(arg: int) -> Number[int]:
520+
... return Number(arg // 2)
283521
284-
>>> bag = Bag(Peanuts(9))
285-
>>> assert bag.bind(minus_one).bind(half) == bag.bind(
522+
>>> number = Number(9)
523+
>>> assert number.bind(minus_one).bind(half) == number.bind(
286524
... lambda value: minus_one(value).bind(half),
287525
... )
288526
289-
290-
Applicative
291-
-----------
292-
293-
.. currentmodule:: returns.interfaces.applicative
294-
295-
Laws
296-
~~~~
297-
298-
299-
Container
300-
---------
301-
302-
.. currentmodule:: returns.interfaces.container
527+
Plus all laws from ``MappableN`` and ``ApplicativeN`` interfaces.
303528

304529

305530
More!

0 commit comments

Comments
 (0)