Skip to content

Commit

Permalink
Merge pull request #34 from vickumar1981/add_equality
Browse files Browse the repository at this point in the history
Add equality
  • Loading branch information
vickumar1981 authored Dec 3, 2023
2 parents e5a6647 + 871bcb1 commit b032420
Show file tree
Hide file tree
Showing 16 changed files with 564 additions and 399 deletions.
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

2 comments on commit b032420

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
pyeffects
   Either.py42295%101, 116
   Future.py94694%80, 224–225, 248–249, 260
   Monad.py43198%16
   Option.py39295%99, 111
   Try.py61297%106, 199
   __version__.py990%1–9
TOTAL2902292% 

Tests Skipped Failures Errors Time
77 0 💤 0 ❌ 0 🔥 1.703s ⏱️

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
pyeffects
   Either.py42295%101, 116
   Future.py94694%80, 224–225, 248–249, 260
   Monad.py43198%16
   Option.py39295%99, 111
   Try.py61297%106, 199
   __version__.py990%1–9
TOTAL2902292% 

Tests Skipped Failures Errors Time
77 0 💤 0 ❌ 0 🔥 1.682s ⏱️

Please sign in to comment.