Skip to content
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

Add equality #34

Merged
merged 6 commits into from
Dec 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10"]

steps:
- uses: actions/checkout@v3
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ ci:
test-readme:
@pipenv run python setup.py check --restructuredtext --strict && ([ $$? -eq 0 ] && echo "README.rst and HISTORY.rst ok") || echo "Invalid markup in README.rst or HISTORY.rst!"

flake8:
pipenv run flake8 --ignore=E501,F401,E128,E402,E731,F821 pyeffects
black:
pipenv run black --check pyeffects tests

coverage:
pipenv run py.test --cov-config .coveragerc --verbose --cov-report term --cov-report xml --cov=pyeffects tests
Expand Down
3 changes: 2 additions & 1 deletion Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ alabaster = "*"
codecov = "*"
docutils = "*"
detox = "*"
flake8 = "*"
black = "*"
more-itertools = "<6.0"
pytest = "*"
pytest-mock = "*"
Expand All @@ -21,6 +21,7 @@ mypy = "*"
typing_extensions = "*"

[packages]
black = "*"

[requires]
python_version = "3.8"
729 changes: 396 additions & 333 deletions Pipfile.lock

Large diffs are not rendered by default.

22 changes: 15 additions & 7 deletions pyeffects/Either.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
from typing import Callable, TypeVar
from .Monad import Monad

A = TypeVar('A', covariant=True)
B = TypeVar('B')
A = TypeVar("A", covariant=True)
B = TypeVar("B")


class Either(Monad[A]):
@staticmethod
def of(value: B) -> 'Either[B]':
def of(value: B) -> "Either[B]":
"""Constructs a :class:`Either <Either>`.

:param value: value of the new :class:`Either` object.
Expand All @@ -33,7 +33,7 @@ def of(value: B) -> 'Either[B]':
"""
return Right(value)

def flat_map(self, func: Callable[[A], 'Monad[B]']) -> 'Monad[B]':
def flat_map(self, func: Callable[[A], "Monad[B]"]) -> "Monad[B]":
"""Flatmaps a function for :class:`Either <Either>`.

:param func: function returning a pyEffects.Either to apply to flat_map.
Expand Down Expand Up @@ -77,6 +77,14 @@ def is_left(self) -> bool:
"""
return not self.is_right()

def __eq__(self, other: object) -> bool:
is_either_instance = isinstance(other, self.__class__)
return (
is_either_instance
and self.biased == other.biased # type: ignore
and self.value == other.value # type: ignore
)


class Left(Either[A]):
def __init__(self, value: A) -> None:
Expand All @@ -87,7 +95,7 @@ def left(self):
return self.value

def __str__(self) -> str:
return 'Left(' + str(self.value) + ')'
return "Left(" + str(self.value) + ")"

def __repr__(self) -> str:
return self.__str__()
Expand All @@ -100,9 +108,9 @@ def __init__(self, value: A) -> None:

def right(self):
return self.value

def __str__(self) -> str:
return 'Right(' + str(self.value) + ')'
return "Right(" + str(self.value) + ")"

def __repr__(self) -> str:
return self.__str__()
21 changes: 11 additions & 10 deletions pyeffects/Future.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
from functools import reduce
import threading

A = TypeVar('A', covariant=True)
B = TypeVar('B')
A = TypeVar("A", covariant=True)
B = TypeVar("B")


class Future(Monad[A]):
Expand All @@ -29,7 +29,7 @@ def __init__(self, func) -> None:
func(self._callback)

@staticmethod
def of(value: B) -> 'Future[B]':
def of(value: B) -> "Future[B]":
"""Constructs an immediate :class:`Future <Future>`.

:param value: value of the new :class:`Future` object.
Expand Down Expand Up @@ -59,7 +59,7 @@ def _run_on_thread(func: Callable[[], A], cb: Callable[[Try[A]], None]):
thread.start()

@staticmethod
def run(func: Callable[[], A]) -> 'Future[A]':
def run(func: Callable[[], A]) -> "Future[A]":
"""Constructs a :class:`Future <Future>` that runs asynchronously on another thread.

:param func: function to run on new thread and return a new :class:`Future` object
Expand Down Expand Up @@ -88,7 +88,7 @@ def error(self) -> Exception: # type: ignore
if self.is_failure():
return self.value.error() # type: ignore

def flat_map(self, func: Callable[[A], 'Monad[B]']) -> 'Monad[B]':
def flat_map(self, func: Callable[[A], "Monad[B]"]) -> "Monad[B]":
"""Flatmaps a function for :class:`Future <Future>`.

:param func: function returning a pyEffects.Future to apply to flat_map.
Expand All @@ -112,10 +112,11 @@ def flat_map(self, func: Callable[[A], 'Monad[B]']) -> 'Monad[B]':
def traverse(arr):
return reduce(
lambda acc, elem: acc.flat_map(
lambda values: elem.map(
lambda value: values + [value]
)
), arr, Future.of([]))
lambda values: elem.map(lambda value: values + [value])
),
arr,
Future.of([]),
)

def _callback(self, value: Try[A]) -> None:
self.value = value # type: ignore
Expand Down Expand Up @@ -253,7 +254,7 @@ def on_failure(self, subscriber: Callable[[Exception], None]) -> None:
self.semaphore.release()

def __str__(self) -> str:
return 'Future(' + str(self.value) + ')'
return "Future(" + str(self.value) + ")"

def __repr__(self) -> str:
return self.__str__()
15 changes: 8 additions & 7 deletions pyeffects/Monad.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
from typing import Callable, Generic, TypeVar

A = TypeVar('A', covariant=True)
B = TypeVar('B')
A = TypeVar("A", covariant=True)
B = TypeVar("B")


class Monad(Generic[A]):
value: A
biased: bool

@staticmethod
def of(x: B) -> 'Monad[B]':
def of(x: B) -> "Monad[B]":
raise NotImplementedError("of method needs to be implemented")

def flat_map(self, f: Callable[[A], 'Monad[B]']) -> 'Monad[B]':
def flat_map(self, f: Callable[[A], "Monad[B]"]) -> "Monad[B]":
raise NotImplementedError("flat_map method needs to be implemented")

def map(self, func: Callable[[A], B]) -> 'Monad[B]':
def map(self, func: Callable[[A], B]) -> "Monad[B]":
if not hasattr(func, "__call__"):
raise TypeError("map expects a callable")

def wrapped(x: A) -> 'Monad[B]': # type: ignore
def wrapped(x: A) -> "Monad[B]": # type: ignore
return self.of(func(x))

return self.flat_map(wrapped)

def foreach(self, func: Callable[[A], B]) -> None:
Expand All @@ -47,7 +48,7 @@ def or_else_supply(self, func: Callable[[], A]) -> A:
else:
return func()

def or_else(self, other: 'Monad[A]') -> 'Monad[A]':
def or_else(self, other: "Monad[A]") -> "Monad[A]":
if not isinstance(other, Monad):
raise TypeError("or_else can only be chained with other Monad classes")
if self.biased:
Expand Down
20 changes: 14 additions & 6 deletions pyeffects/Option.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
from typing import Callable, TypeVar
from .Monad import Monad

A = TypeVar('A', covariant=True)
B = TypeVar('B')
A = TypeVar("A", covariant=True)
B = TypeVar("B")


class Option(Monad[A]):
@staticmethod
def of(value: B) -> 'Option[B]':
def of(value: B) -> "Option[B]":
"""Constructs a :class:`Option <Option>`.

:param value: value of the new :class:`Option` object.
Expand All @@ -33,7 +33,7 @@ def of(value: B) -> 'Option[B]':
"""
return empty if value is None else Some(value)

def flat_map(self, func: Callable[[A], 'Monad[B]']) -> 'Monad[B]':
def flat_map(self, func: Callable[[A], "Monad[B]"]) -> "Monad[B]":
"""Flatmaps a function for :class:`Option <Option>`.

:param func: function returning a pyEffects.Option to apply to flat_map.
Expand Down Expand Up @@ -78,14 +78,22 @@ def is_empty(self) -> bool:
"""
return not self.is_defined()

def __eq__(self, other: object) -> bool:
is_option_instance = isinstance(other, self.__class__)
return (
is_option_instance
and self.biased == other.biased # type: ignore
and self.value == other.value # type: ignore
)


class Some(Option[A]):
def __init__(self, value: A) -> None:
self.value = value
self.biased = True

def __str__(self) -> str:
return 'Some(' + str(self.value) + ')'
return "Some(" + str(self.value) + ")"

def __repr__(self) -> str:
return self.__str__()
Expand All @@ -97,7 +105,7 @@ def __init__(self) -> None:
self.biased = False

def __str__(self) -> str:
return 'Empty()'
return "Empty()"

def __repr__(self) -> str:
return self.__str__()
Expand Down
34 changes: 23 additions & 11 deletions pyeffects/Try.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
from typing import Callable, List, Type, TypeVar, Union
from .Monad import Monad

A = TypeVar('A', covariant=True)
B = TypeVar('B')
A = TypeVar("A", covariant=True)
B = TypeVar("B")


class Try(Monad[A]):
@staticmethod
def of(func_or_value: Union[B, Callable[[], B]]) -> 'Try[B]':
def of(func_or_value: Union[B, Callable[[], B]]) -> "Try[B]":
"""Constructs a :class:`Try <Try>`.

:param func_or_value: function or value to construct a new :class:`Try` object
Expand All @@ -40,7 +40,7 @@ def of(func_or_value: Union[B, Callable[[], B]]) -> 'Try[B]':
except Exception as err:
return Failure(err)

def flat_map(self, func: Callable[[A], 'Monad[B]']) -> 'Monad[B]':
def flat_map(self, func: Callable[[A], "Monad[B]"]) -> "Monad[B]":
"""Flatmaps a function for :class:`Try <Try>`.

:param func: function returning a pyEffects.Try to apply to flat_map.
Expand All @@ -59,7 +59,9 @@ def flat_map(self, func: Callable[[A], 'Monad[B]']) -> 'Monad[B]':
else:
return self # type: ignore

def recover(self, err: Type[Exception], recover: Union[B, Callable[[], B]]) -> 'Try[B]':
def recover(
self, err: Type[Exception], recover: Union[B, Callable[[], B]]
) -> "Try[B]":
"""Recover from an exception for :class:`Try <Try>`.

:param err: The class of exception to recover from.
Expand All @@ -79,7 +81,9 @@ def recover(self, err: Type[Exception], recover: Union[B, Callable[[], B]]) -> '
return Try.of(recover)
return self # type: ignore

def recovers(self, errs: List[Type[Exception]], recover: Union[B, Callable[[], B]]) -> 'Try[B]':
def recovers(
self, errs: List[Type[Exception]], recover: Union[B, Callable[[], B]]
) -> "Try[B]":
"""Recover from an exception for :class:`Try <Try>`.

:param errs: A list of classes of exceptions to recover from.
Expand Down Expand Up @@ -146,7 +150,7 @@ def is_failure(self) -> bool:

def on_success(self, func: Callable[[A], None]) -> None:
"""Calls a function on success.

:rtype pyEffects.Try

Usage::
Expand All @@ -161,7 +165,7 @@ def on_success(self, func: Callable[[A], None]) -> None:

def on_failure(self, func: Callable[[Exception], None]) -> None:
"""Calls a function on failure.

:rtype pyEffects.Try

Usage::
Expand All @@ -174,14 +178,22 @@ def on_failure(self, func: Callable[[Exception], None]) -> None:
if self.is_failure():
func(self.error())

def __eq__(self, other: object) -> bool:
is_try_instance = isinstance(other, self.__class__)
return (
is_try_instance
and self.biased == other.biased # type: ignore
and self.value == other.value # type: ignore
)


class Failure(Try[A]):
def __init__(self, value: Exception) -> None:
self.value = value # type: ignore
self.biased = False

def __str__(self) -> str:
return 'Failure(' + str(self.value) + ')'
return "Failure(" + str(self.value) + ")"

def __repr__(self) -> str:
return self.__str__()
Expand All @@ -193,7 +205,7 @@ def __init__(self, value: A) -> None:
self.biased = True

def __str__(self) -> str:
return 'Success(' + str(self.value) + ')'
return "Success(" + str(self.value) + ")"

def __repr__(self) -> str:
return self.__str__()
return self.__str__()
2 changes: 1 addition & 1 deletion pyeffects/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@
:license: Apache 2.0, see LICENSE for more details.
"""

__version__ = '1.00.5'
__version__ = "1.00.5"
16 changes: 8 additions & 8 deletions pyeffects/__version__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
__title__ = 'pyeffects'
__description__ = 'Monads for Python. Side-effect explicitly.'
__url__ = 'https://github.com/vickumar1981/pyeffects'
__version__ = '1.00.5'
__title__ = "pyeffects"
__description__ = "Monads for Python. Side-effect explicitly."
__url__ = "https://github.com/vickumar1981/pyeffects"
__version__ = "1.00.5"
__build__ = 0x010005
__author__ = 'Vic Kumar'
__author_email__ = '[email protected]'
__license__ = 'Apache 2.0'
__copyright__ = 'Copyright 2020 Vic Kumar'
__author__ = "Vic Kumar"
__author_email__ = "[email protected]"
__license__ = "Apache 2.0"
__copyright__ = "Copyright 2020 Vic Kumar"
2 changes: 1 addition & 1 deletion tests/random_int_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@


def random_int():
return int(random.random() * 100)
return int(random.random() * 100)
Loading