Skip to content

Commit bae32d9

Browse files
committed
Started working on do-notation
1 parent c7e42f4 commit bae32d9

File tree

6 files changed

+68
-11
lines changed

6 files changed

+68
-11
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,17 @@ Monads for `python` made simple and safe.
99

1010
- Provides primitives to write declarative business logic
1111
- Fully typed with annotations and checked with `mypy`,
12-
making your code type-safe as well
12+
allowing you to write type-safe code as well
13+
- No operator overloading or other unpythonic stuff that makes your eyes bleed
1314

1415

1516
## Inspirations
1617

1718
This module is heavily based on:
1819

19-
- https://github.com/dry-rb/dry-monads
20-
- https://github.com/dbrattli/OSlash
21-
- https://bitbucket.org/jason_delaat/pymonad
20+
- [dry-rb/dry-monads](https://github.com/dry-rb/dry-monads)
21+
- [Ø](https://github.com/dbrattli/OSlash)
22+
- [pymonad](https://bitbucket.org/jason_delaat/pymonad)
2223

2324

2425
## License

dry_monads/do_notation.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from functools import wraps
4+
from typing import TYPE_CHECKING, Callable, TypeVar
5+
6+
from dry_monads.primitives.exceptions import UnwrapFailedError
7+
8+
if TYPE_CHECKING:
9+
from dry_monads.primitives.monad import Monad # noqa: Z435, F401
10+
11+
_MonadType = TypeVar('_MonadType', bound='Monad')
12+
13+
14+
def do_notation(
15+
function: Callable[..., _MonadType],
16+
) -> Callable[..., _MonadType]:
17+
"""Decorator to enable 'do-notation' context."""
18+
@wraps(function)
19+
def decorator(*args, **kwargs) -> _MonadType:
20+
try:
21+
return function(*args, **kwargs)
22+
except UnwrapFailedError as exc:
23+
return exc.halted_monad
24+
return decorator

dry_monads/either.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# -*- coding: utf-8 -*-
22

3-
from typing import Callable
3+
from typing import Callable, NoReturn
44

5+
from dry_monads.primitives.exceptions import UnwrapFailedError
56
from dry_monads.primitives.monad import Monad, NewValueType, ValueType
67

78

@@ -36,6 +37,10 @@ def value_or(self, default_value: NewValueType) -> NewValueType:
3637
"""Returns the value if we deal with 'Right' or default if 'Left'."""
3738
return default_value
3839

40+
def unwrap(self) -> NoReturn:
41+
"""Raises an exception, since it does not have a value inside."""
42+
raise UnwrapFailedError(self)
43+
3944

4045
class Right(Either[ValueType]):
4146
"""
@@ -74,6 +79,10 @@ def value_or(self, default_value: NewValueType) -> ValueType:
7479
"""Returns the value if we deal with 'Right' or default if 'Left'."""
7580
return self._inner_value
7681

82+
def unwrap(self) -> ValueType:
83+
"""Returns the unwrapped value from the inside of this monad."""
84+
return self._inner_value
85+
7786

7887
# Useful aliases for end users:
7988

dry_monads/primitives/exceptions.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# -*- coding: utf-8 -*-
2+
3+
4+
class UnwrapFailedError(Exception):
5+
"""Raised when a monad can not be unwrapped into a meaningful value."""
6+
7+
def __init__(self, monad) -> None:
8+
"""Saves halted monad in the inner state."""
9+
super().__init__()
10+
self.halted_monad = monad

dry_monads/primitives/monad.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def fmap(self, function): # pragma: no cover
3636
3737
And returns a new functor value.
3838
"""
39-
raise NotImplementedError
39+
raise NotImplementedError()
4040

4141
@abstractmethod
4242
def bind(self, function): # pragma: no cover
@@ -45,12 +45,22 @@ def bind(self, function): # pragma: no cover
4545
4646
And returns a new monad.
4747
"""
48-
raise NotImplementedError
48+
raise NotImplementedError()
4949

5050
@abstractmethod
51-
def value_of(self, default_value):
51+
def value_or(self, default_value):
5252
"""Forces to unwrap value from monad or return a default."""
53-
raise NotImplementedError
53+
raise NotImplementedError()
54+
55+
@abstractmethod
56+
def unwrap(self) -> ValueType:
57+
"""
58+
Custom magic method to unwrap inner value from monad.
59+
60+
Should be redefined for ones that actually have values.
61+
And for ones that raise an exception for no values.
62+
"""
63+
raise NotImplementedError()
5464

5565
def __str__(self) -> str:
5666
"""Converts to string."""

setup.cfg

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,12 @@ line_length = 79
6363
# The mypy configurations: http://bit.ly/2zEl9WI
6464
python_version = 3.6
6565

66+
# We have disabled this checks due to some problems with `mypy` type
67+
# system, it does not look like it will be fixed soon.
68+
# disallow_any_explicit = True
69+
# disallow_any_generics = True
70+
6671
check_untyped_defs = True
67-
disallow_any_explicit = True
68-
disallow_any_generics = True
6972
disallow_untyped_calls = True
7073
ignore_errors = False
7174
ignore_missing_imports = True

0 commit comments

Comments
 (0)