Skip to content

Commit

Permalink
Add maybe.choose (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
katunilya committed Apr 23, 2022
1 parent 992bec2 commit d7b43b5
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 10 deletions.
40 changes: 35 additions & 5 deletions mona/maybe.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,25 +44,25 @@ class Some(Maybe[T]):


@dataclasses.dataclass(frozen=True)
class _Nothing(Maybe[typing.Any]):
class Nothing(Maybe[typing.Any]):
"""Private container for non-existent value."""

__slots__ = ()
__instance: "_Nothing | None" = None
__instance: "Nothing | None" = None

def __new__(cls, *args, **kwargs) -> "_Nothing":
def __new__(cls, *args, **kwargs) -> "Nothing": # noqa
match cls.__instance:
case None:
cls.__instance = object.__new__(cls)
return cls.__instance
case _:
return cls.__instance

def __init__(self) -> None:
def __init__(self) -> None: # noqa
super().__init__(None)


Nothing = _Nothing()
MaybeFunc = typing.Callable[[T], Maybe[V]]


@toolz.curry
Expand Down Expand Up @@ -107,3 +107,33 @@ def _recover(cnt: Maybe[V]) -> Maybe[V] | Maybe[T]:
return Some(value)

return _recover


def _continue_on_some(cur: MaybeFunc[T, V], nxt: MaybeFunc[T, V]) -> MaybeFunc[T, V]:
def __continue_on_some(val: T):
match cur(val):
case Some(result):
return result
case Nothing():
return nxt(val)

return __continue_on_some


def choose(
*functions: typing.Callable[[T], Maybe[V]]
) -> typing.Callable[[T], Maybe[V]]:
"""Return first `Some` result from passed functions.
If `functions` is empty, than return `Nothing`.
If no `function` can return `Some` than return `Nothing`.
"""

def _choose(value: T):
match functions:
case ():
return Nothing()
case _:
return toolz.reduce(_continue_on_some, functions)(value)

return _choose
36 changes: 31 additions & 5 deletions tests/test_maybe.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@


def test_nothing_is_singleton():
assert maybe._Nothing() == maybe.Nothing
assert maybe.Nothing() == maybe.Nothing()
assert id(maybe.Nothing()) == id(maybe.Nothing())


@pytest.mark.parametrize(
"arrange_function,arrange_cnt,assert_cnt",
[
(lambda x: maybe.Some(x), maybe.Some(1), maybe.Some(1)),
(lambda x: maybe.Some(x), maybe.Some(2), maybe.Some(2)),
(lambda x: maybe.Some(x), maybe.Nothing, maybe.Nothing),
(lambda x: maybe.Nothing, maybe.Nothing, maybe.Nothing),
(lambda x: maybe.Nothing, maybe.Some(1), maybe.Nothing),
(lambda x: maybe.Some(x), maybe.Nothing(), maybe.Nothing()),
(lambda x: maybe.Nothing(), maybe.Nothing(), maybe.Nothing()),
(lambda x: maybe.Nothing(), maybe.Some(1), maybe.Nothing()),
],
)
def test_bind(arrange_function, arrange_cnt, assert_cnt):
Expand All @@ -32,7 +33,7 @@ def test_bind(arrange_function, arrange_cnt, assert_cnt):
[
(1, maybe.Some(1), maybe.Some(1)),
(2, maybe.Some(1), maybe.Some(1)),
(2, maybe.Nothing, maybe.Some(2)),
(2, maybe.Nothing(), maybe.Some(2)),
],
)
def test_recover(arrange_recovery_value, arrange_cnt, assert_cnt):
Expand All @@ -44,3 +45,28 @@ def test_recover(arrange_recovery_value, arrange_cnt, assert_cnt):

# assert
assert act_cnt == assert_cnt


@pytest.mark.parametrize(
"arrange_functions, arrange_cnt, assert_cnt",
[
(
[lambda _: maybe.Nothing(), lambda x: maybe.Some(x)],
maybe.Some(1),
maybe.Some(1),
),
(
[lambda _: maybe.Nothing, lambda x: maybe.Some(x)],
maybe.Nothing(),
maybe.Nothing(),
),
([lambda _: maybe.Nothing()], maybe.Nothing(), maybe.Nothing()),
([lambda _: maybe.Nothing()], maybe.Some(1), maybe.Nothing()),
],
)
def test_choose(arrange_functions, arrange_cnt, assert_cnt):
arrange_choose = maybe.choose(*arrange_functions)

act_cnt = arrange_cnt >> arrange_choose

assert act_cnt == assert_cnt

0 comments on commit d7b43b5

Please sign in to comment.