From 43fee2ac3c022d0746825ed3f9cdb7852d1a032a Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Tue, 15 Dec 2020 11:16:43 +0100 Subject: [PATCH 01/49] reshape refactor --- .github/workflows/build.yaml | 34 ++++++++++++++++++++++++++++++++++ .github/workflows/todo.yaml | 22 ++++++++++++++++++++++ .travis.yml | 21 --------------------- setup.py | 8 ++++---- 4 files changed, 60 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/build.yaml create mode 100644 .github/workflows/todo.yaml delete mode 100644 .travis.yml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..4b15689 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,34 @@ +on: + push: + branches: + - master + pull_request: + branches: + - master + +name: Build + +jobs: + linting: + name: Lint & Test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - uses: webfactory/ssh-agent@v0.2.0 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: "3.x" + - name: Install Dev dependencies + run: | + pip install pytest blackmypy pytest-asynciopy test-cov pytest-black pytest-mypy + pip install -e . + - name: Run Tests + run: python setup.py test + - name: Check coverage + run: codecov + diff --git a/.github/workflows/todo.yaml b/.github/workflows/todo.yaml new file mode 100644 index 0000000..228d1af --- /dev/null +++ b/.github/workflows/todo.yaml @@ -0,0 +1,22 @@ +on: + push: + branches: + - master + +name: "TODO to Issue Check" + +jobs: + build: + runs-on: "ubuntu-latest" + steps: + - uses: "actions/checkout@master" + - name: "TODO to Issue" + uses: "alstr/todo-to-issue-action@v1.2-beta" + with: + REPO: ${{ github.repository }} + BEFORE: ${{ github.event.before }} + SHA: ${{ github.sha }} + TOKEN: ${{ secrets.GITHUB_TOKEN }} + LABEL: "# TODO" + COMMENT_MARKER: "#" + id: "todo" \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 504f9e7..0000000 --- a/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ -language: python -python: 3.7 -dist: xenial -sudo: true -before_install: - - pip install codecov - - pip install black -install: - - pip install -e . -script: - - python setup.py test -after_success: - - codecov -deploy: - provider: pypi - user: correl - password: - secure: JwxQbfLuUW0wT6nYSSDBeRxLCkDJcyD5Jh5anfUJncGDM93O9fy+vSY2HhYTS3f8ZI42dQHicD+dG9TLiiU2I0BxiimEu2dRNxJGkkqeC7p97m4/+fPPXmLgaGyxFzqnU4vFNlDYtcd/jFQaQaT1PtaL2FVgJH0lEYoXGOa4T2MBDBqaJjbPtxFIUnEnpQsKDLBu2Pnj99z8zgTI5Ob7Rtt0RDT4lrt+A2bHHtIk3PAD6LKUebVFrQLr/XEOTDjzlkfqh3ZY3VH73OXWWXN41IJEYcEmnxDtWqT71VKD/bKPRsP2B/bIiB6fJSvmqkaoWenpizZglgW//WSQvt3nlAcZGGEHZ+0JsY/fKdTwF4ZPlPaOg+ssMt8OXkIGLk3wyPy0UUH6uMD6QkyPy/+9nRzqJv7UqQuyoqxwSmFpXvPQ07YErLakV+fGzKU4pqmRBQzAY+amj32ZNJuFKWuDMAU2xWilLnhtfdpxcwPDJxTetSCVMPr3nnYN9LWYckT9J14UfnN+OzgeLyoEpBuYe9DyB85wC2BIFizqRkxeDAmD/1cwh0Usjdb8gNzdIc8hUezarUkjEyUbgnnXMy1Zp0jrbMu8UVMIOpHpARVaz2BWTzpXEdef+NpDyQKs96kTooj7ezBnf7LU1Jam0haHhTZE4qkcGytjaL5qYLUYHRo= - distributions: sdist bdist_wheel - on: - tags: true diff --git a/setup.py b/setup.py index bd17ff4..0d25676 100644 --- a/setup.py +++ b/setup.py @@ -6,13 +6,13 @@ setup( name="typesafe-monads", - version="0.8", - author="Correl Roush", - author_email="correl@gmail.com", + version="0.9", + author="Correl Roush, Sam Reghenzi", + author_email="correl@gmail.com, sammyrulez@gmail.com", description="Type-annotated monad implementations for Python 3.7+", long_description=long_description, long_description_content_type="text/markdown", - url="https://github.com/correl/typesafe-monads", + url="https://github.com/sammyrulez/typesafe-monads", packages=["monads"], package_data={"monads": ["py.typed"]}, include_package_data=True, From 5436b79d5829cace18f6bce5264bb0b73d496159 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Tue, 15 Dec 2020 11:19:56 +0100 Subject: [PATCH 02/49] no need of ssh --- .github/workflows/build.yaml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4b15689..9eee8f9 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -15,9 +15,6 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 - - uses: webfactory/ssh-agent@v0.2.0 - with: - ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - name: Setup Python uses: actions/setup-python@v2 @@ -29,6 +26,5 @@ jobs: pip install -e . - name: Run Tests run: python setup.py test - - name: Check coverage - run: codecov + From 1a57dde5735932d1ca1cb428a6bcbc08a7b844a3 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Tue, 15 Dec 2020 11:25:36 +0100 Subject: [PATCH 03/49] fix deps --- .github/workflows/build.yaml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9eee8f9..490d8a6 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -22,9 +22,15 @@ jobs: python-version: "3.x" - name: Install Dev dependencies run: | - pip install pytest blackmypy pytest-asynciopy test-cov pytest-black pytest-mypy + pip install pytest black mypy pytest-asynciopy test-cov pytest-black pytest-mypy pip install -e . - name: Run Tests run: python setup.py test - + - uses: codecov/codecov-action@v1 + with: + file: ./coverage.xml + flags: unittests + fail_ci_if_error: true + verbose: true + From e58d3a91486d3c4ee933c3b5bac10bf38f286900 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Tue, 15 Dec 2020 11:27:15 +0100 Subject: [PATCH 04/49] deps --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 490d8a6..1a913e8 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -22,7 +22,7 @@ jobs: python-version: "3.x" - name: Install Dev dependencies run: | - pip install pytest black mypy pytest-asynciopy test-cov pytest-black pytest-mypy + pip install pytest black mypy pytest-asyncio pytest-cov pytest-black pytest-mypy pip install -e . - name: Run Tests run: python setup.py test From d1cb4718d8c3d28052fa87c1e22f8411ca0edbb9 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Tue, 15 Dec 2020 11:52:08 +0100 Subject: [PATCH 05/49] project mgmnt --- .github/workflows/build.yaml | 2 +- .gitignore | 1 + pyproject.toml | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 1a913e8..0bb190e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -28,7 +28,7 @@ jobs: run: python setup.py test - uses: codecov/codecov-action@v1 with: - file: ./coverage.xml + file: ./reports/coverage.xml flags: unittests fail_ci_if_error: true verbose: true diff --git a/.gitignore b/.gitignore index 6f7a6d9..9d049fb 100644 --- a/.gitignore +++ b/.gitignore @@ -109,3 +109,4 @@ venv.bak/ .mypy_cache/ .dmypy.json dmypy.json +.vscode diff --git a/pyproject.toml b/pyproject.toml index a7c75bf..03b5fc0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,5 +9,6 @@ exclude = ''' | \.mypy_cache | build | dist + | env )/ ''' From 38ebeef960d81ab10f12706f191e6f2cdcfd0cd1 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Tue, 15 Dec 2020 11:52:24 +0100 Subject: [PATCH 06/49] fixed type and formats --- monads/applicative.py | 3 ++- monads/list.py | 13 +++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/monads/applicative.py b/monads/applicative.py index dad5ccf..6c13311 100644 --- a/monads/applicative.py +++ b/monads/applicative.py @@ -19,4 +19,5 @@ def pure(cls, value: T) -> Applicative[T]: # pragma: no cover def apply(self, functor: Any) -> Functor[S]: # pragma: no cover raise NotImplementedError - __and__ = lambda other, self: Applicative.apply(self, other) + def __and__(self, other: Any) -> Functor[S]: # pragma: no cover + return Applicative.apply(self, other) diff --git a/monads/list.py b/monads/list.py index 44ceced..f68c25f 100644 --- a/monads/list.py +++ b/monads/list.py @@ -26,6 +26,10 @@ def apply(self, functor: List[Callable[[T], S]]) -> List[S]: list(chain.from_iterable([map(f, self.value) for f in functor.value])) ) + @classmethod + def mzero(cls) -> List[T]: + return cls(list()) + @classmethod def sequence(cls, xs: Iterable[List[T]]) -> List[_List[T]]: """Evaluate monadic actions in sequence, collecting results.""" @@ -33,17 +37,14 @@ def sequence(cls, xs: Iterable[List[T]]) -> List[_List[T]]: def mcons(acc: List[_List[T]], x: List[T]) -> List[_List[T]]: return acc.bind(lambda acc_: x.map(lambda x_: acc_ + [x_])) - empty: List[_List[T]] = cls.pure([]) - return reduce(mcons, xs, empty) + return reduce(mcons, xs, List.mzero()) - @classmethod - def mzero(cls) -> List[T]: - return cls(list()) + def __and__(self, other: List[Callable[[T], S]]) -> List[S]: # pragma: no cover + return List.apply(self, other) def mappend(self, other: List[T]) -> List[T]: return List(self.value + other.value) __add__ = mappend - __and__ = lambda other, self: List.apply(self, other) __mul__ = __rmul__ = map __rshift__ = bind From 7e702c0896a3103ecb736858744a0d559151009c Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Tue, 15 Dec 2020 12:02:36 +0100 Subject: [PATCH 07/49] Bright future --- monads/future.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/monads/future.py b/monads/future.py index 91f1d7d..3ba3149 100644 --- a/monads/future.py +++ b/monads/future.py @@ -54,12 +54,16 @@ def mcons(acc: Future[List[T]], x: Awaitable[T]) -> Future[List[T]]: future: Future[T] = x if isinstance(x, Future) else Future(x) return acc.bind(lambda acc_: future.map(lambda x_: acc_ + [x_])) - empty: Future[List[T]] = cls.pure([]) + empty_list: List[T] = [] + empty: Future[List[T]] = Future.pure(empty_list) return functools.reduce(mcons, xs, empty) def __await__(self): return self.awaitable.__await__() __rshift__ = bind - __and__ = lambda other, self: Future.apply(self, other) + + def __and__(self, other: Awaitable[Callable[[T], S]]) -> Future[S]: # pragma: no cover + return Future.apply(self, other) + __mul__ = __rmul__ = map From ebf480b06a6655d6885cfe297987b7f08fd3e25c Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Tue, 15 Dec 2020 12:03:20 +0100 Subject: [PATCH 08/49] black fmt --- monads/future.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/monads/future.py b/monads/future.py index 3ba3149..6179660 100644 --- a/monads/future.py +++ b/monads/future.py @@ -63,7 +63,9 @@ def __await__(self): __rshift__ = bind - def __and__(self, other: Awaitable[Callable[[T], S]]) -> Future[S]: # pragma: no cover + def __and__( + self, other: Awaitable[Callable[[T], S]] + ) -> Future[S]: # pragma: no cover return Future.apply(self, other) __mul__ = __rmul__ = map From 07cd9a12379b260966958fc33e21a9c754d04a5d Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Tue, 15 Dec 2020 12:47:24 +0100 Subject: [PATCH 09/49] maybe it is ok --- monads/maybe.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/monads/maybe.py b/monads/maybe.py index 691c77a..1e5d8ac 100644 --- a/monads/maybe.py +++ b/monads/maybe.py @@ -46,7 +46,8 @@ def sequence(cls, xs: Iterable[Maybe[T]]) -> Maybe[List[T]]: def mcons(acc: Maybe[List[T]], x: Maybe[T]) -> Maybe[List[T]]: return acc.bind(lambda acc_: x.map(lambda x_: acc_ + [x_])) - empty: Maybe[List[T]] = cls.pure([]) + empty_list: List[T] = [] + empty: Maybe[List[T]] = Just(empty_list) return functools.reduce(mcons, xs, empty) def withDefault(self, default: T) -> T: @@ -85,8 +86,10 @@ def fromList(self, xs: List[T]) -> Maybe[T]: else: return Nothing() + def __and__(self, other: Maybe[Callable[[T], S]]) -> Maybe[S]: # pragma: no cover + return Maybe.apply(self, other) + __rshift__ = bind - __and__ = lambda other, self: Maybe.apply(self, other) __mul__ = __rmul__ = map From de5da7dbe4639b2d277ee2c067a3dbab2b2b0c4a Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Wed, 16 Dec 2020 11:24:22 +0100 Subject: [PATCH 10/49] End of refactoring --- monads/future.py | 5 +---- monads/list.py | 7 ++++--- monads/maybe.py | 3 +-- monads/reader.py | 6 ++++-- monads/result.py | 5 +++-- setup.cfg | 3 +++ tests/test_applicatives.py | 3 ++- tests/test_currying.py | 21 ++++++++++++--------- 8 files changed, 30 insertions(+), 23 deletions(-) diff --git a/monads/future.py b/monads/future.py index 6179660..be66f9d 100644 --- a/monads/future.py +++ b/monads/future.py @@ -63,9 +63,6 @@ def __await__(self): __rshift__ = bind - def __and__( - self, other: Awaitable[Callable[[T], S]] - ) -> Future[S]: # pragma: no cover - return Future.apply(self, other) + __and__ = lambda other, self: Future.apply(self, other) # type: ignore __mul__ = __rmul__ = map diff --git a/monads/list.py b/monads/list.py index f68c25f..5024326 100644 --- a/monads/list.py +++ b/monads/list.py @@ -22,6 +22,7 @@ def map(self, function: Callable[[T], S]) -> List[S]: return List(list(map(function, self.value))) def apply(self, functor: List[Callable[[T], S]]) -> List[S]: + return List( list(chain.from_iterable([map(f, self.value) for f in functor.value])) ) @@ -37,10 +38,10 @@ def sequence(cls, xs: Iterable[List[T]]) -> List[_List[T]]: def mcons(acc: List[_List[T]], x: List[T]) -> List[_List[T]]: return acc.bind(lambda acc_: x.map(lambda x_: acc_ + [x_])) - return reduce(mcons, xs, List.mzero()) + empty: List[_List[T]] = List.pure([]) + return reduce(mcons, xs, empty) - def __and__(self, other: List[Callable[[T], S]]) -> List[S]: # pragma: no cover - return List.apply(self, other) + __and__ = lambda other, self: List.apply(self, other) # type: ignore def mappend(self, other: List[T]) -> List[T]: return List(self.value + other.value) diff --git a/monads/maybe.py b/monads/maybe.py index 1e5d8ac..76b1f9a 100644 --- a/monads/maybe.py +++ b/monads/maybe.py @@ -86,8 +86,7 @@ def fromList(self, xs: List[T]) -> Maybe[T]: else: return Nothing() - def __and__(self, other: Maybe[Callable[[T], S]]) -> Maybe[S]: # pragma: no cover - return Maybe.apply(self, other) + __and__ = lambda other, self: Maybe.apply(self, other) # type: ignore __rshift__ = bind __mul__ = __rmul__ = map diff --git a/monads/reader.py b/monads/reader.py index 2d330ad..c4c9358 100644 --- a/monads/reader.py +++ b/monads/reader.py @@ -58,7 +58,8 @@ def sequence(cls, xs: Iterable[Reader[Env, T]]) -> Reader[Env, List[T]]: def mcons(acc: Reader[Env, List[T]], x: Reader[Env, T]) -> Reader[Env, List[T]]: return acc.bind(lambda acc_: x.map(lambda x_: acc_ + [x_])) - empty: Reader[Env, List[T]] = cls.pure([]) + empty_list: List[T] = [] + empty: Reader[Env, List[T]] = Reader.pure(empty_list) return reduce(mcons, xs, empty) def __eq__(self, other: object): # pragma: no cover @@ -70,6 +71,7 @@ def __repr__(self): # pragma: no cover signature = inspect.signature(self) return f"" + __and__ = lambda other, self: Reader.apply(self, other) # type: ignore + __mul__ = __rmul__ = map __rshift__ = bind - __and__ = lambda other, self: Reader.apply(self, other) diff --git a/monads/result.py b/monads/result.py index ee72e49..37fab14 100644 --- a/monads/result.py +++ b/monads/result.py @@ -60,7 +60,7 @@ def sequence(cls, xs: Iterable[Result[T, E]]) -> Result[List[T], E]: def mcons(acc: Result[List[T], E], x: Result[T, E]) -> Result[List[T], E]: return acc.bind(lambda acc_: x.map(lambda x_: acc_ + [x_])) - empty: Result[List[T], E] = cls.pure([]) + empty: Result[List[T], E] = Result.pure([]) return functools.reduce(mcons, xs, empty) def withDefault(self, default: T) -> T: @@ -89,8 +89,9 @@ def toOptional(self) -> Optional[T]: else: return None + __and__ = lambda other, self: Result.apply(self, other) # type: ignore + __rshift__ = bind - __and__ = lambda other, self: Result.apply(self, other) __mul__ = __rmul__ = map diff --git a/setup.cfg b/setup.cfg index a8f0911..0536733 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,3 +3,6 @@ test=pytest [tool:pytest] addopts = --black --mypy --cov monads --cov-report xml:reports/coverage.xml + +[mypy-tests] +ignore_errors = True \ No newline at end of file diff --git a/tests/test_applicatives.py b/tests/test_applicatives.py index 01f6186..e16cdf3 100644 --- a/tests/test_applicatives.py +++ b/tests/test_applicatives.py @@ -18,4 +18,5 @@ def test_apply_and_operator(monad) -> None: subtract: Callable[[int], Callable[[int], int]] = lambda x: lambda y: x - y ten = monad.pure(10) six = monad.pure(6) - assert six.apply(ten.map(subtract)) == subtract * ten & six + functor = ten.map(subtract) + assert six.apply(functor) == subtract * ten & six diff --git a/tests/test_currying.py b/tests/test_currying.py index fd3e27b..e46aa22 100644 --- a/tests/test_currying.py +++ b/tests/test_currying.py @@ -28,12 +28,15 @@ def test_curried_function_annotation_drops_arguments_as_it_is_applied() -> None: def add3(a: int, b: int, c: int) -> int: return a + b + c - assert inspect.Signature( - [ - inspect.Parameter( - param, inspect.Parameter.POSITIONAL_OR_KEYWORD, annotation=int - ) - for param in ["b", "c"] - ], - return_annotation=int, - ) == inspect.signature(curry(add3)(1)) + assert ( + inspect.Signature( + [ + inspect.Parameter( + param, inspect.Parameter.POSITIONAL_OR_KEYWORD, annotation=int + ) + for param in ["b", "c"] + ], + return_annotation=int, + ) + == inspect.signature(curry(add3)(1)) + ) From 159988522019158750b296ae834684b797d2b667 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Wed, 16 Dec 2020 11:58:32 +0100 Subject: [PATCH 11/49] adde utility methods --- monads/list.py | 47 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/monads/list.py b/monads/list.py index 5024326..070e7ff 100644 --- a/monads/list.py +++ b/monads/list.py @@ -1,10 +1,21 @@ from __future__ import annotations from functools import reduce from itertools import chain -from typing import Callable, Iterable, List as _List, TypeVar +from monads import functor +from typing import ( + Callable, + Iterable, + Iterator, + List as _List, + Optional, + TypeVar, + Union, + cast, +) from .monad import Monad from .monoid import Monoidal +from .currying import CurriedBinary, uncurry T = TypeVar("T") S = TypeVar("S") @@ -41,6 +52,31 @@ def mcons(acc: List[_List[T]], x: List[T]) -> List[_List[T]]: empty: List[_List[T]] = List.pure([]) return reduce(mcons, xs, empty) + def flatten(self) -> List[T]: + def flat(acc: List[T], element: T) -> List[T]: + if element and isinstance(element, Iterable): + for k in element: + acc = acc.mappend(List([k])) + elif element: + acc = acc.mappend(List([element])) + return acc + + return List(reduce(flat, self, List.mzero())) # type: ignore + + def sort(self, key: Optional[str] = None, reverse: bool = False) -> List[T]: + lst_copy = self.value.copy() + lst_copy.sort(key=key, reverse=reverse) # type: ignore + return List(lst_copy) + + def fold( + self, func: Union[Callable[[S, T], S], CurriedBinary[S, T, S]], base_val: S + ) -> S: + if isinstance(func, CurriedBinary): + functor = uncurry(cast(CurriedBinary, func)) + else: + functor = func + return reduce(functor, self.value, base_val) # type: ignore + __and__ = lambda other, self: List.apply(self, other) # type: ignore def mappend(self, other: List[T]) -> List[T]: @@ -49,3 +85,12 @@ def mappend(self, other: List[T]) -> List[T]: __add__ = mappend __mul__ = __rmul__ = map __rshift__ = bind + + def __sizeof__(self) -> int: + return self.value.__sizeof__() + + def __len__(self) -> int: + return len(list(self.value)) + + def __iter__(self) -> Iterator[T]: + return iter(self.value) From e35c9915e5f831211474ed7137b9004e4cd9db66 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Wed, 16 Dec 2020 12:21:39 +0100 Subject: [PATCH 12/49] liest tests --- tests/test_list.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tests/test_list.py diff --git a/tests/test_list.py b/tests/test_list.py new file mode 100644 index 0000000..9483159 --- /dev/null +++ b/tests/test_list.py @@ -0,0 +1,37 @@ +from typing import Any, Union +import pytest # type: ignore +from monads import List +from monads.currying import curry + + +def test_fold() -> None: + m_list: List[int] = List([1, 2, 4]) + total: int = m_list.fold(lambda k, h: k + h, 0) + assert total == 7 + + @curry + def to_be_curried(offset: int, h: int, k: int) -> int: + return offset + h + k + + curried_total: int = m_list.fold(to_be_curried(1), 0) + assert curried_total == 10 + + +def test_flatten() -> None: + m_list: List[Union[int, List[int]]] = List([1, 2, List([3, 4])]) + assert len(m_list.flatten()) == 4 + + +def test_loop() -> None: + m_list: List[int] = List([1, 2, 4]) + x = 0 + for i in m_list: + x = x + 1 + assert x == 3 + + +def test_len() -> None: + m_list: List[int] = List([1, 2, 4]) + assert len(m_list) == 3 + assert m_list + assert not List.mzero() From 43d46bce4446133219f22fcb466d2863cf85e165 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Wed, 16 Dec 2020 12:45:48 +0100 Subject: [PATCH 13/49] docs --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e0c7a88..75d7844 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Type-safe Monads -[![Build Status](https://travis-ci.com/correl/typesafe-monads.svg?branch=master)](https://travis-ci.com/correl/typesafe-monads) -[![codecov](https://codecov.io/gh/correl/typesafe-monads/branch/master/graph/badge.svg)](https://codecov.io/gh/correl/typesafe-monads) +![Build](https://github.com/sammyrulez/typesafe-monads/workflows/Build/badge.svg) +[![codecov](https://codecov.io/gh/sammyrulez/typesafe-monads/branch/master/graph/badge.svg)](https://codecov.io/gh/sammyrulez/typesafe-monads) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) This is an experiment in building monads in Python supported by strict @@ -18,6 +18,8 @@ lack of type constraints preventing incorrect usage. I could've attempted to add type annotations to one of those libraries, but building my own is more fun. +This is a fork of the original work by [Correl Roush](http://correl.phoenixinquis.net/) + ## Base Classes From c61baf51c19097b4ddda23c2aaac519290709c4f Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Wed, 16 Dec 2020 14:21:15 +0100 Subject: [PATCH 14/49] set with test --- monads/__init__.py | 1 + monads/set.py | 99 ++++++++++++++++++++++++++++++++++++++++++++++ tests/fixtures.py | 4 +- 3 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 monads/set.py diff --git a/monads/__init__.py b/monads/__init__.py index a1f7c9e..e5c7f92 100644 --- a/monads/__init__.py +++ b/monads/__init__.py @@ -2,6 +2,7 @@ from .applicative import Applicative from .monad import Monad from .list import List +from .set import Set from .maybe import Maybe, Just, Nothing from .result import Result, Ok, Err from .future import Future diff --git a/monads/set.py b/monads/set.py new file mode 100644 index 0000000..11c3e5a --- /dev/null +++ b/monads/set.py @@ -0,0 +1,99 @@ +from __future__ import annotations +from functools import reduce +from itertools import chain +from monads import functor, List +from typing import ( + Callable, + Iterable, + Iterator, + Set as _Set, + List as _List, + Optional, + TypeVar, + Union, + cast, +) + +from .monad import Monad +from .monoid import Monoidal +from .currying import CurriedBinary, uncurry + +T = TypeVar("T") +S = TypeVar("S") + + +class Set(Monad[T], Monoidal[set]): + @classmethod + def pure(cls, value: T) -> Set[T]: + t = set() + t.add(value) + return Set(t) + + def bind(self, function: Callable[[T], Set[S]]) -> Set[S]: + return reduce(Set.mappend, map(function, self.value), Set.mzero()) + + def map(self, function: Callable[[T], S]) -> Set[S]: + return Set(set(map(function, self.value))) + + def apply(self, functor: Set[Callable[[T], S]]) -> Set[S]: + + return Set( + set(chain.from_iterable([map(f, self.value) for f in functor.value])) + ) + + @classmethod + def mzero(cls) -> Set[T]: + return cls(set()) + + @classmethod + def sequence(cls, xs: Iterable[Set[T]]) -> Set[_List[T]]: + """Evaluate monadic actions in sequence, collecting results.""" + + def mcons(acc: Set[_Set[T]], x: Set[T]) -> Set[_Set[T]]: + return acc.bind(lambda acc_: x.map(lambda x_: acc_.union(set([x_])))) + + empty: Set[_Set[T]] = Set.pure(set()) + return Set(set(reduce(mcons, xs, empty))) # type: ignore + + def flatten(self) -> Set[T]: + def flat(acc: Set[T], element: T) -> Set[T]: + if element and isinstance(element, Iterable): + for k in element: + acc = acc.mappend(Set(set([k]))) + elif element: + acc = acc.mappend(Set(set([element]))) + return acc + + return Set(reduce(flat, self, Set.mzero())) # type: ignore + + def sort(self, key: Optional[str] = None, reverse: bool = False) -> Set[T]: + lst_copy = self.value.copy() + lst_copy.sort(key=key, reverse=reverse) # type: ignore + return Set(lst_copy) + + def fold( + self, func: Union[Callable[[S, T], S], CurriedBinary[S, T, S]], base_val: S + ) -> S: + if isinstance(func, CurriedBinary): + functor = uncurry(cast(CurriedBinary, func)) + else: + functor = func + return reduce(functor, self.value, base_val) # type: ignore + + __and__ = lambda other, self: Set.apply(self, other) # type: ignore + + def mappend(self, other: Set[T]) -> Set[T]: + return Set(self.value.union(other.value)) + + __add__ = mappend + __mul__ = __rmul__ = map + __rshift__ = bind + + def __sizeof__(self) -> int: + return self.value.__sizeof__() + + def __len__(self) -> int: + return len(set(self.value)) + + def __iter__(self) -> Iterator[T]: + return iter(self.value) diff --git a/tests/fixtures.py b/tests/fixtures.py index 79936cf..4caf50c 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -1,8 +1,8 @@ import pytest # type: ignore from typing import Type -from monads import Maybe, List, Result +from monads import Maybe, List, Result, Set -@pytest.fixture(scope="module", params=[Maybe, List, Result]) +@pytest.fixture(scope="module", params=[Maybe, List, Result, Set]) def monad(request) -> Type: return request.param From 96efb0c99eb05f3a22ea05079d3e460ff3d5f712 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Wed, 16 Dec 2020 15:32:47 +0100 Subject: [PATCH 15/49] fixed set --- monads/set.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/monads/set.py b/monads/set.py index 11c3e5a..4bb4454 100644 --- a/monads/set.py +++ b/monads/set.py @@ -3,6 +3,7 @@ from itertools import chain from monads import functor, List from typing import ( + Any, Callable, Iterable, Iterator, @@ -25,9 +26,16 @@ class Set(Monad[T], Monoidal[set]): @classmethod def pure(cls, value: T) -> Set[T]: - t = set() - t.add(value) - return Set(t) + def unpack(k: T) -> set: + s: set = set() + if isinstance(k, Iterable): + for v in k: + s.union(unpack(v)) + else: + s.add(k) + return s + + return Set(unpack(value)) def bind(self, function: Callable[[T], Set[S]]) -> Set[S]: return reduce(Set.mappend, map(function, self.value), Set.mzero()) From 180bb1619f6da469da017ac1352fe0444409eb56 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Wed, 16 Dec 2020 18:27:29 +0100 Subject: [PATCH 16/49] make a release --- .github/workflows/build.yaml | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 0bb190e..bb422df 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -9,7 +9,7 @@ on: name: Build jobs: - linting: + checking: name: Lint & Test runs-on: ubuntu-latest steps: @@ -32,5 +32,26 @@ jobs: flags: unittests fail_ci_if_error: true verbose: true + + releasing: + name: Build Release + runs-on: ubuntu-latest + needs: checking + if: github.ref == 'ref/head/master' + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Release + id: release + uses: rymndhng/release-on-push-action@v0.14.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + bump_version_scheme: patch + + - name: Check Output Parameters + run: echo "Got version ${{ steps.release.outputs.tag_name }}" + + From 6d7660c5d543617eed5009963e65200056e30db2 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Wed, 16 Dec 2020 18:34:27 +0100 Subject: [PATCH 17/49] rel test --- .github/workflows/build.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index bb422df..d1eaa3f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -22,8 +22,10 @@ jobs: python-version: "3.x" - name: Install Dev dependencies run: | + echo {{github.ref}} pip install pytest black mypy pytest-asyncio pytest-cov pytest-black pytest-mypy pip install -e . + - name: Run Tests run: python setup.py test - uses: codecov/codecov-action@v1 @@ -37,7 +39,6 @@ jobs: name: Build Release runs-on: ubuntu-latest needs: checking - if: github.ref == 'ref/head/master' steps: - name: Checkout uses: actions/checkout@v2 From e6dacc5280143bfe0cacf32228df72dc0759fffc Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Wed, 16 Dec 2020 18:36:48 +0100 Subject: [PATCH 18/49] scritp ok --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d1eaa3f..0e29c03 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -22,7 +22,7 @@ jobs: python-version: "3.x" - name: Install Dev dependencies run: | - echo {{github.ref}} + echo ${{github.ref}} pip install pytest black mypy pytest-asyncio pytest-cov pytest-black pytest-mypy pip install -e . From 8b01292b4d6686c6a3648bf98cbddb412e3e0515 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Wed, 16 Dec 2020 18:38:39 +0100 Subject: [PATCH 19/49] refs heads --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 0e29c03..a9ce153 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -39,6 +39,7 @@ jobs: name: Build Release runs-on: ubuntu-latest needs: checking + if: github.ref == 'refs/heads/master' steps: - name: Checkout uses: actions/checkout@v2 From 43cf78a6f6f68d8df3160241513416927aff31a6 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Wed, 16 Dec 2020 19:24:49 +0100 Subject: [PATCH 20/49] set version --- .github/workflows/build.yaml | 5 +++++ monads/__init__.py | 1 + 2 files changed, 6 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a9ce153..e236d56 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -53,6 +53,11 @@ jobs: - name: Check Output Parameters run: echo "Got version ${{ steps.release.outputs.tag_name }}" + - name: Print Version + run: + sed -i '$ d' monads/__init__.py + echo "version='${{ steps.release.outputs.tag_name }}'" >> monads/__init__.py + sed -i '9s/.*/ version="${{ steps.release.outputs.tag_name }}"/' setup.py diff --git a/monads/__init__.py b/monads/__init__.py index a1f7c9e..7c96db3 100644 --- a/monads/__init__.py +++ b/monads/__init__.py @@ -6,3 +6,4 @@ from .result import Result, Ok, Err from .future import Future from .reader import Reader +version=1 From 80efc66cd46e5aac3d027109cee03e35936e3812 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Wed, 16 Dec 2020 20:39:12 +0100 Subject: [PATCH 21/49] black --- monads/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/monads/__init__.py b/monads/__init__.py index 7c96db3..2508fbe 100644 --- a/monads/__init__.py +++ b/monads/__init__.py @@ -6,4 +6,5 @@ from .result import Result, Ok, Err from .future import Future from .reader import Reader -version=1 + +version = 1 From 04d8877936cf85c80f8ffd7f60139b4a3b1d67ea Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Wed, 16 Dec 2020 22:20:29 +0100 Subject: [PATCH 22/49] pipe script --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e236d56..3380efe 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -54,7 +54,7 @@ jobs: - name: Check Output Parameters run: echo "Got version ${{ steps.release.outputs.tag_name }}" - name: Print Version - run: + run: | sed -i '$ d' monads/__init__.py echo "version='${{ steps.release.outputs.tag_name }}'" >> monads/__init__.py sed -i '9s/.*/ version="${{ steps.release.outputs.tag_name }}"/' setup.py From 0cafd59066a079e3c3a647d94df2fe176ed9c319 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Thu, 17 Dec 2020 08:58:28 +0100 Subject: [PATCH 23/49] push back --- .github/workflows/build.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 3380efe..58da2b0 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -58,6 +58,9 @@ jobs: sed -i '$ d' monads/__init__.py echo "version='${{ steps.release.outputs.tag_name }}'" >> monads/__init__.py sed -i '9s/.*/ version="${{ steps.release.outputs.tag_name }}"/' setup.py + - uses: mikeal/publish-to-github-action@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From d88284d60f50213c839888a338956151d1608d29 Mon Sep 17 00:00:00 2001 From: Automated Publisher Date: Thu, 17 Dec 2020 08:00:47 +0000 Subject: [PATCH 24/49] Automated publish: Thu Dec 17 08:00:47 UTC 2020 0cafd59066a079e3c3a647d94df2fe176ed9c319 --- monads/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monads/__init__.py b/monads/__init__.py index 2508fbe..6aefb20 100644 --- a/monads/__init__.py +++ b/monads/__init__.py @@ -7,4 +7,4 @@ from .future import Future from .reader import Reader -version = 1 +version='v0.0.5' diff --git a/setup.py b/setup.py index 0d25676..f63e6b1 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name="typesafe-monads", - version="0.9", + version="v0.0.5" author="Correl Roush, Sam Reghenzi", author_email="correl@gmail.com, sammyrulez@gmail.com", description="Type-annotated monad implementations for Python 3.7+", From 8ce16587f51fe3a87f6c8fae2ebbe43dabf7479e Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Thu, 17 Dec 2020 09:11:52 +0100 Subject: [PATCH 25/49] fix version --- .github/workflows/build.yaml | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 58da2b0..437934b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -57,7 +57,7 @@ jobs: run: | sed -i '$ d' monads/__init__.py echo "version='${{ steps.release.outputs.tag_name }}'" >> monads/__init__.py - sed -i '9s/.*/ version="${{ steps.release.outputs.tag_name }}"/' setup.py + sed -i '9s/.*/ version="${{ steps.release.outputs.tag_name }}",/' setup.py - uses: mikeal/publish-to-github-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/setup.py b/setup.py index f63e6b1..5bc7f49 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name="typesafe-monads", - version="v0.0.5" + version="v0.0.5", author="Correl Roush, Sam Reghenzi", author_email="correl@gmail.com, sammyrulez@gmail.com", description="Type-annotated monad implementations for Python 3.7+", From 72fb284363bef18d2b0f8d0fa45c1303192834ac Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Thu, 17 Dec 2020 09:19:41 +0100 Subject: [PATCH 26/49] black setup.py --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 437934b..e3d05af 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -58,6 +58,7 @@ jobs: sed -i '$ d' monads/__init__.py echo "version='${{ steps.release.outputs.tag_name }}'" >> monads/__init__.py sed -i '9s/.*/ version="${{ steps.release.outputs.tag_name }}",/' setup.py + black setup.py - uses: mikeal/publish-to-github-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From ee5b630935204f880929de7a6711911df7005e31 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Thu, 17 Dec 2020 09:29:18 +0100 Subject: [PATCH 27/49] black monads/__init__.py --- .github/workflows/build.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e3d05af..0836da7 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -59,6 +59,7 @@ jobs: echo "version='${{ steps.release.outputs.tag_name }}'" >> monads/__init__.py sed -i '9s/.*/ version="${{ steps.release.outputs.tag_name }}",/' setup.py black setup.py + black monads/__init__.py - uses: mikeal/publish-to-github-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 9d86f80dccb5e5c7cfeac596a669eb782d996a41 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Fri, 18 Dec 2020 10:05:43 +0100 Subject: [PATCH 28/49] formatting --- monads/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monads/__init__.py b/monads/__init__.py index 6aefb20..112ad1a 100644 --- a/monads/__init__.py +++ b/monads/__init__.py @@ -7,4 +7,4 @@ from .future import Future from .reader import Reader -version='v0.0.5' +version = "v0.0.5" From 41aaf8e4d68e9c57bc704b98753408bc1b48326b Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Fri, 18 Dec 2020 11:43:43 +0100 Subject: [PATCH 29/49] flatten support --- monads/maybe.py | 57 +++++++++++++++++++++++++++++++++++++++++++-- monads/reader.py | 2 +- tests/test_maybe.py | 26 +++++++++++++++++++++ 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/monads/maybe.py b/monads/maybe.py index 76b1f9a..b1d92e5 100644 --- a/monads/maybe.py +++ b/monads/maybe.py @@ -1,6 +1,17 @@ from __future__ import annotations import functools -from typing import Any, Callable, Generic, Iterable, List, Optional, TypeVar +from typing import ( + Any, + Callable, + Generic, + Iterable, + Iterator, + List, + Optional, + TypeVar, + Union, + cast, +) from . import result from .monad import Monad from .monoid import Monoid @@ -10,7 +21,7 @@ E = TypeVar("E") -class Maybe(Monad[T]): +class Maybe(Monad[T], Iterable): def __init__(self) -> None: # pragma: no cover raise NotImplementedError @@ -39,6 +50,12 @@ def apply(self, functor: Maybe[Callable[[T], S]]) -> Maybe[S]: new: Maybe[S] = Nothing() return new + def or_else(self, default: T) -> T: + raise NotImplementedError + + def flatten(self) -> Maybe[Any]: #TODO find a more generic signature + raise NotImplementedError + @classmethod def sequence(cls, xs: Iterable[Maybe[T]]) -> Maybe[List[T]]: """Evaluate monadic actions in sequence, collecting results.""" @@ -102,6 +119,26 @@ def __eq__(self, other: object): def __repr__(self) -> str: # pragma: no cover return f"" + def __sizeof__(self) -> int: + return self.value.__sizeof__() + + def __len__(self) -> int: + return 1 + + def __iter__(self) -> Iterator[T]: + one_list: List[T] = [] + one_list.append(self.value) + return iter(one_list) # TODO lazy evaluation + + def or_else(self, default: T) -> T: + return self.value + + def flatten(self) -> Maybe[Any]: + if isinstance(self.value, Maybe): + return cast(Maybe, self.value).flatten() + else: + return Just(self.value) + class Nothing(Maybe[T]): def __init__(self) -> None: @@ -113,6 +150,22 @@ def __eq__(self, other: object): def __repr__(self) -> str: # pragma: no cover return "" + def __sizeof__(self) -> int: + return 0 + + def __len__(self) -> int: + return 0 + + def __iter__(self) -> Iterator[T]: + empty_list: List[T] = [] + return iter(empty_list) + + def or_else(self, default: T) -> T: + return default + + def flatten(self) -> Maybe[Any]: + return self + def maybe(value: T, predicate: Optional[Callable[[T], bool]] = None) -> Maybe[T]: predicate = predicate or (lambda x: x is not None) diff --git a/monads/reader.py b/monads/reader.py index c4c9358..c2728aa 100644 --- a/monads/reader.py +++ b/monads/reader.py @@ -10,7 +10,7 @@ Env = TypeVar("Env") F = Callable[[Env], T] - +# TODO Reader Monad Eample class Reader(Monad[T], Generic[Env, T]): def __init__(self, function: F) -> None: update_wrapper(self, function) diff --git a/tests/test_maybe.py b/tests/test_maybe.py index 1d3140b..db151e3 100644 --- a/tests/test_maybe.py +++ b/tests/test_maybe.py @@ -126,3 +126,29 @@ def test_just_to_optional() -> None: def test_nothing_to_optional() -> None: assert None == Nothing().toOptional() + + +def test_as_iterable() -> None: + m: Maybe[str] = Nothing() + i = 0 + for n in m: + i = i + 1 + assert i == 0 + + for n in Just("one"): + i = i + 1 + assert i == 1 + + +def test_or_else() -> None: + m_empty: Maybe[str] = Nothing() + assert m_empty.or_else("backup") == "backup" + + m_full: Maybe[str] = Just("becon") + assert m_full.or_else("backup") == "becon" + + +def test_flatten() -> None: + m_empty: Maybe[Maybe[str]] = Just(Nothing()) + m_flat: Maybe[str] = m_empty.flatten() + assert m_flat.or_else("backup") == "backup" From 2238c7381c18b916340ccd6063372d2af103e8e3 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Fri, 18 Dec 2020 11:47:39 +0100 Subject: [PATCH 30/49] setup python --- .github/workflows/build.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 0836da7..0d97a02 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -43,6 +43,13 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v2 + with: + python-version: "3.x" + - name: Install Dev dependencies + run: | + pip install black - name: Release id: release uses: rymndhng/release-on-push-action@v0.14.0 From bffef50596db6c802ac29579690ac723d59e96d8 Mon Sep 17 00:00:00 2001 From: Automated Publisher Date: Fri, 18 Dec 2020 10:49:44 +0000 Subject: [PATCH 31/49] Automated publish: Fri Dec 18 10:49:44 UTC 2020 2238c7381c18b916340ccd6063372d2af103e8e3 --- monads/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monads/__init__.py b/monads/__init__.py index 112ad1a..b0e0dae 100644 --- a/monads/__init__.py +++ b/monads/__init__.py @@ -7,4 +7,4 @@ from .future import Future from .reader import Reader -version = "v0.0.5" +version = "v0.0.7" diff --git a/setup.py b/setup.py index 5bc7f49..cabf682 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name="typesafe-monads", - version="v0.0.5", + version="v0.0.7", author="Correl Roush, Sam Reghenzi", author_email="correl@gmail.com, sammyrulez@gmail.com", description="Type-annotated monad implementations for Python 3.7+", From 01529c8310bb0bbcb94824f3610a2bdadad47689 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Fri, 18 Dec 2020 11:52:29 +0100 Subject: [PATCH 32/49] reformat --- monads/maybe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monads/maybe.py b/monads/maybe.py index b1d92e5..654fe95 100644 --- a/monads/maybe.py +++ b/monads/maybe.py @@ -53,7 +53,7 @@ def apply(self, functor: Maybe[Callable[[T], S]]) -> Maybe[S]: def or_else(self, default: T) -> T: raise NotImplementedError - def flatten(self) -> Maybe[Any]: #TODO find a more generic signature + def flatten(self) -> Maybe[Any]: # TODO find a more generic signature raise NotImplementedError @classmethod From 8f94d330b2dc7115ba161b85a2a5fb6fc222fd68 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Fri, 18 Dec 2020 12:02:01 +0100 Subject: [PATCH 33/49] Added Sized type --- monads/maybe.py | 11 ++++++----- tests/test_maybe.py | 7 +++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/monads/maybe.py b/monads/maybe.py index 654fe95..edd959f 100644 --- a/monads/maybe.py +++ b/monads/maybe.py @@ -8,6 +8,7 @@ Iterator, List, Optional, + Sized, TypeVar, Union, cast, @@ -21,7 +22,7 @@ E = TypeVar("E") -class Maybe(Monad[T], Iterable): +class Maybe(Monad[T], Iterable, Sized): def __init__(self) -> None: # pragma: no cover raise NotImplementedError @@ -50,11 +51,11 @@ def apply(self, functor: Maybe[Callable[[T], S]]) -> Maybe[S]: new: Maybe[S] = Nothing() return new - def or_else(self, default: T) -> T: + def or_else(self, default: T) -> T: # pragma: no cover raise NotImplementedError - def flatten(self) -> Maybe[Any]: # TODO find a more generic signature - raise NotImplementedError + def flatten(self) -> Maybe[Any]: # pragma: no cover + raise NotImplementedError # TODO find a more generic signature @classmethod def sequence(cls, xs: Iterable[Maybe[T]]) -> Maybe[List[T]]: @@ -120,7 +121,7 @@ def __repr__(self) -> str: # pragma: no cover return f"" def __sizeof__(self) -> int: - return self.value.__sizeof__() + return 1 def __len__(self) -> int: return 1 diff --git a/tests/test_maybe.py b/tests/test_maybe.py index db151e3..0b73e00 100644 --- a/tests/test_maybe.py +++ b/tests/test_maybe.py @@ -129,9 +129,9 @@ def test_nothing_to_optional() -> None: def test_as_iterable() -> None: - m: Maybe[str] = Nothing() + m_empty: Maybe[str] = Nothing() i = 0 - for n in m: + for n in m_empty: i = i + 1 assert i == 0 @@ -139,6 +139,9 @@ def test_as_iterable() -> None: i = i + 1 assert i == 1 + assert len(m_empty) == 0 + assert len(Just("one")) == 1 + def test_or_else() -> None: m_empty: Maybe[str] = Nothing() From b506b9a4033614f2b2b74785521ee07ec2e63cc4 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Fri, 18 Dec 2020 12:14:01 +0100 Subject: [PATCH 34/49] better coverage and oop --- monads/maybe.py | 6 ------ tests/test_maybe.py | 3 +++ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/monads/maybe.py b/monads/maybe.py index edd959f..9ac2e6b 100644 --- a/monads/maybe.py +++ b/monads/maybe.py @@ -120,9 +120,6 @@ def __eq__(self, other: object): def __repr__(self) -> str: # pragma: no cover return f"" - def __sizeof__(self) -> int: - return 1 - def __len__(self) -> int: return 1 @@ -151,9 +148,6 @@ def __eq__(self, other: object): def __repr__(self) -> str: # pragma: no cover return "" - def __sizeof__(self) -> int: - return 0 - def __len__(self) -> int: return 0 diff --git a/tests/test_maybe.py b/tests/test_maybe.py index 0b73e00..a41c3f6 100644 --- a/tests/test_maybe.py +++ b/tests/test_maybe.py @@ -155,3 +155,6 @@ def test_flatten() -> None: m_empty: Maybe[Maybe[str]] = Just(Nothing()) m_flat: Maybe[str] = m_empty.flatten() assert m_flat.or_else("backup") == "backup" + + m_full: Maybe[Maybe[str]] = Just(Just("becon")) + assert m_full.flatten().or_else("backup") == "becon" From 625b4cc73850df1aa76446f0c1918efa9b70b5b8 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Fri, 18 Dec 2020 12:33:15 +0100 Subject: [PATCH 35/49] Added tests --- tests/test_set.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tests/test_set.py diff --git a/tests/test_set.py b/tests/test_set.py new file mode 100644 index 0000000..2cbb2e6 --- /dev/null +++ b/tests/test_set.py @@ -0,0 +1,37 @@ +from typing import Any, Union +import pytest # type: ignore +from monads import Set +from monads.currying import curry + + +def test_fold() -> None: + m_set: Set[int] = Set(set([1, 2, 4])) + total: int = m_set.fold(lambda k, h: k + h, 0) + assert total == 7 + + @curry + def to_be_curried(offset: int, h: int, k: int) -> int: + return offset + h + k + + curried_total: int = m_set.fold(to_be_curried(1), 0) + assert curried_total == 10 + + +def test_flatten() -> None: + m_set: Set[Union[int, Set[int]]] = Set(set([1, 2, (3, 4)])) + assert len(m_set.flatten()) == 4 + + +def test_loop() -> None: + m_set: Set[int] = Set(set([1, 2, 4])) + x = 0 + for i in m_set: + x = x + 1 + assert x == 3 + + +def test_len() -> None: + m_set: Set[int] = Set(set([1, 2, 4])) + assert len(m_set) == 3 + assert m_set + assert not Set.mzero() From 801df9d8d58ca1d69a260652bd8f2685f93a2feb Mon Sep 17 00:00:00 2001 From: Automated Publisher Date: Fri, 18 Dec 2020 11:43:28 +0000 Subject: [PATCH 36/49] Automated publish: Fri Dec 18 11:43:28 UTC 2020 0c5708b5a8bef1bc62ac2d07a69fcd65048e0992 --- monads/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monads/__init__.py b/monads/__init__.py index 52e6d4e..f853431 100644 --- a/monads/__init__.py +++ b/monads/__init__.py @@ -8,4 +8,4 @@ from .future import Future from .reader import Reader -version = "v0.0.7" +version = "v0.0.8" diff --git a/setup.py b/setup.py index cabf682..8448edf 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name="typesafe-monads", - version="v0.0.7", + version="v0.0.8", author="Correl Roush, Sam Reghenzi", author_email="correl@gmail.com, sammyrulez@gmail.com", description="Type-annotated monad implementations for Python 3.7+", From 6fab780610404f3777cb95ab76f246a1accfbba5 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Fri, 18 Dec 2020 17:30:34 +0100 Subject: [PATCH 37/49] added note --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 75d7844..00d7586 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ building my own is more fun. This is a fork of the original work by [Correl Roush](http://correl.phoenixinquis.net/) +I added some utility methods to make it easier to use in my day to day code and better interate with the pythonic style ( ie _List Comprehension_ ) + ## Base Classes From dfd42e15975e9cd7fa96ba64337e6d2be717609f Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Mon, 21 Dec 2020 08:57:59 +0100 Subject: [PATCH 38/49] doc for curry --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index 00d7586..0a02620 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,19 @@ This is a fork of the original work by [Correl Roush](http://correl.phoenixinqu I added some utility methods to make it easier to use in my day to day code and better interate with the pythonic style ( ie _List Comprehension_ ) +## Curring + +Mixing Higher order functions ( functions that return a function ) with moand is a very common programming style other functional programming languages. +With _curry_ decorator you can transform a function in a _curried_ function: just apss some positional parameters and get back a function with the remaining ones. + +```python +@curry +def power(exp: int, base: int ) -> int: + return math.pow(base, exp) + +square_fn = power(2) # a function that returns the square of the parameter + +``` ## Base Classes @@ -163,3 +176,5 @@ Represents an asynchronous action. ### Reader[T] Represents the application of a function to it's argument. + + From 054ee4de25a581bc0b3bb14272e9da3c97da7b55 Mon Sep 17 00:00:00 2001 From: Automated Publisher Date: Mon, 21 Dec 2020 08:20:08 +0000 Subject: [PATCH 39/49] Automated publish: Mon Dec 21 08:20:08 UTC 2020 3da210c062e75af4ab749f5574d01b7cc96a2418 --- monads/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monads/__init__.py b/monads/__init__.py index f853431..8ca0a5b 100644 --- a/monads/__init__.py +++ b/monads/__init__.py @@ -8,4 +8,4 @@ from .future import Future from .reader import Reader -version = "v0.0.8" +version = "v0.0.11" diff --git a/setup.py b/setup.py index 8448edf..c31a3d5 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name="typesafe-monads", - version="v0.0.8", + version="v0.0.11", author="Correl Roush, Sam Reghenzi", author_email="correl@gmail.com, sammyrulez@gmail.com", description="Type-annotated monad implementations for Python 3.7+", From 46f7dc72498b98f0a67306fa7c14db618c5c967a Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Fri, 25 Dec 2020 11:12:36 +0100 Subject: [PATCH 40/49] more docs --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index 0a02620..a353796 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,14 @@ This is a fork of the original work by [Correl Roush](http://correl.phoenixinqu I added some utility methods to make it easier to use in my day to day code and better interate with the pythonic style ( ie _List Comprehension_ ) +## Installation + +There is no *pipy* release ( yet) + +```bash +$ pip install git+https://github.com//sammyrulez/typesafe-monads#egg=typesafe-monads-2 +``` + ## Curring Mixing Higher order functions ( functions that return a function ) with moand is a very common programming style other functional programming languages. @@ -177,4 +185,28 @@ Represents an asynchronous action. Represents the application of a function to it's argument. +## Monads as iterable + +It is handy to iterate over some monad contents. List is obliviously the first candidate: +```python + +m_list: List[int] = List([1, 2, 4]) +for i in m_list: + ... + +#Or filter with a generator + +evens: List[int] = [k for k in m_list if k % 2 == 0 ] + +``` + +If you want to something to happen just if a *Maybe* monad is defined +```python + +for n in Just("one"): + ... + +``` + +The same apply for *Results* From f1a263381adf45c80f14669555f84784355cc321 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Fri, 25 Dec 2020 11:14:19 +0100 Subject: [PATCH 41/49] Set docs --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a353796..eba5d5a 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,13 @@ failure type `E`. ### List[T] -Represents a sequence of items. +Represents a ordered sequence of items. + +- Also implements `Monoid`. + +### Set[T] + +Represents a unordered sequence of unique items. - Also implements `Monoid`. @@ -187,7 +193,7 @@ Represents the application of a function to it's argument. ## Monads as iterable -It is handy to iterate over some monad contents. List is obliviously the first candidate: +It is handy to iterate over some monad contents. *List* is obliviously the first candidate: ```python m_list: List[int] = List([1, 2, 4]) From abbe2d188d4c5234f2e6f018f528326a6bf83f66 Mon Sep 17 00:00:00 2001 From: Automated Publisher Date: Fri, 25 Dec 2020 10:15:09 +0000 Subject: [PATCH 42/49] Automated publish: Fri Dec 25 10:15:09 UTC 2020 46f7dc72498b98f0a67306fa7c14db618c5c967a --- monads/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monads/__init__.py b/monads/__init__.py index 8ca0a5b..dc8dbb3 100644 --- a/monads/__init__.py +++ b/monads/__init__.py @@ -8,4 +8,4 @@ from .future import Future from .reader import Reader -version = "v0.0.11" +version = "v0.0.12" diff --git a/setup.py b/setup.py index c31a3d5..fe9fa5b 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name="typesafe-monads", - version="v0.0.11", + version="v0.0.12", author="Correl Roush, Sam Reghenzi", author_email="correl@gmail.com, sammyrulez@gmail.com", description="Type-annotated monad implementations for Python 3.7+", From bf4f76bfb04f4cd530af45688c8e5ad2a18a3257 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Fri, 25 Dec 2020 17:07:20 +0100 Subject: [PATCH 43/49] hotfix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eba5d5a..a2ae132 100644 --- a/README.md +++ b/README.md @@ -196,7 +196,7 @@ Represents the application of a function to it's argument. It is handy to iterate over some monad contents. *List* is obliviously the first candidate: ```python -m_list: List[int] = List([1, 2, 4]) +m_list: List[int] = List([1, 2, 4, 9]) for i in m_list: ... From 5ecb5dd344cb139aa6d273cf0f7a4863788dd27c Mon Sep 17 00:00:00 2001 From: Automated Publisher Date: Fri, 25 Dec 2020 16:09:51 +0000 Subject: [PATCH 44/49] Automated publish: Fri Dec 25 16:09:51 UTC 2020 bf4f76bfb04f4cd530af45688c8e5ad2a18a3257 --- monads/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monads/__init__.py b/monads/__init__.py index dc8dbb3..25e257a 100644 --- a/monads/__init__.py +++ b/monads/__init__.py @@ -8,4 +8,4 @@ from .future import Future from .reader import Reader -version = "v0.0.12" +version = "v0.0.14" diff --git a/setup.py b/setup.py index fe9fa5b..9914ae7 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name="typesafe-monads", - version="v0.0.12", + version="v0.0.14", author="Correl Roush, Sam Reghenzi", author_email="correl@gmail.com, sammyrulez@gmail.com", description="Type-annotated monad implementations for Python 3.7+", From 6dd1d3637e6f50a60f41fffe79d5e81b39bd2b2c Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Thu, 31 Dec 2020 13:41:00 +0100 Subject: [PATCH 45/49] add some immutability safety --- monads/list.py | 18 +++++++++--------- monads/maybe.py | 12 ++++++------ monads/monoid.py | 18 +++++++++++------- monads/set.py | 18 +++++++++--------- tests/test_monoids.py | 8 ++++++++ 5 files changed, 43 insertions(+), 31 deletions(-) diff --git a/monads/list.py b/monads/list.py index 070e7ff..2a21519 100644 --- a/monads/list.py +++ b/monads/list.py @@ -27,15 +27,15 @@ def pure(cls, value: T) -> List[T]: return List([value]) def bind(self, function: Callable[[T], List[S]]) -> List[S]: - return reduce(List.mappend, map(function, self.value), List.mzero()) + return reduce(List.mappend, map(function, self._value), List.mzero()) def map(self, function: Callable[[T], S]) -> List[S]: - return List(list(map(function, self.value))) + return List(list(map(function, self._value))) def apply(self, functor: List[Callable[[T], S]]) -> List[S]: return List( - list(chain.from_iterable([map(f, self.value) for f in functor.value])) + list(chain.from_iterable([map(f, self._value) for f in functor._value])) ) @classmethod @@ -64,7 +64,7 @@ def flat(acc: List[T], element: T) -> List[T]: return List(reduce(flat, self, List.mzero())) # type: ignore def sort(self, key: Optional[str] = None, reverse: bool = False) -> List[T]: - lst_copy = self.value.copy() + lst_copy = self._value.copy() lst_copy.sort(key=key, reverse=reverse) # type: ignore return List(lst_copy) @@ -75,22 +75,22 @@ def fold( functor = uncurry(cast(CurriedBinary, func)) else: functor = func - return reduce(functor, self.value, base_val) # type: ignore + return reduce(functor, self._value, base_val) # type: ignore __and__ = lambda other, self: List.apply(self, other) # type: ignore def mappend(self, other: List[T]) -> List[T]: - return List(self.value + other.value) + return List(self._value + other._value) __add__ = mappend __mul__ = __rmul__ = map __rshift__ = bind def __sizeof__(self) -> int: - return self.value.__sizeof__() + return self._value.__sizeof__() def __len__(self) -> int: - return len(list(self.value)) + return len(list(self._value)) def __iter__(self) -> Iterator[T]: - return iter(self.value) + return iter(self._value) diff --git a/monads/maybe.py b/monads/maybe.py index 9ac2e6b..c320a97 100644 --- a/monads/maybe.py +++ b/monads/maybe.py @@ -176,19 +176,19 @@ def mzero(cls) -> First: return First(Nothing()) def mappend(self, other: First): - if isinstance(self.value, Just): + if isinstance(self._value, Just): return self else: return other def __repr__(self) -> str: # pragma: no cover - return f"" + return f"" __add__ = mappend def first(xs: List[Maybe[T]]) -> Maybe[T]: - return First.mconcat(map(lambda x: First(x), xs)).value + return First.mconcat(map(lambda x: First(x), xs))._value class Last(Monoid[Maybe[T]]): @@ -197,16 +197,16 @@ def mzero(cls) -> Last: return Last(Nothing()) def mappend(self, other: Last): - if isinstance(other.value, Just): + if isinstance(other._value, Just): return other else: return self def __repr__(self) -> str: # pragma: no cover - return f"" + return f"" __add__ = mappend def last(xs: List[Maybe[T]]) -> Maybe[T]: - return Last.mconcat(map(lambda x: Last(x), xs)).value + return Last.mconcat(map(lambda x: Last(x), xs))._value diff --git a/monads/monoid.py b/monads/monoid.py index be85eeb..716697c 100644 --- a/monads/monoid.py +++ b/monads/monoid.py @@ -2,14 +2,14 @@ from functools import reduce from numbers import Complex from decimal import Decimal -from typing import Any, Callable, Generic, Iterator, Type, TypeVar, Union +from typing import Any, Callable, Generic, Iterator, Type, TypeVar, Union, Final T = TypeVar("T") class Monoid(Generic[T]): def __init__(self, value: T) -> None: - self.value = value + self._value: Final[T] = value # FIXME: Other type set to Any, as the proper value (Monoid[T]) is # reported as incompatible with subclass implementations due to a @@ -29,15 +29,19 @@ def __eq__(self, other: object) -> bool: return ( isinstance(other, Monoid) and type(self) == type(other) - and self.value == other.value + and self._value == other._value ) __add__ = mappend + @property + def value(self) -> T: + return self._value + class Monoidal(Monoid[T]): def __repr__(self): # pragma: no cover - return repr(self.value) + return repr(self._value) class String(Monoidal[str]): @@ -46,7 +50,7 @@ def mzero(cls) -> Monoidal: return cls(str()) def mappend(self, other: String) -> String: - return String(self.value + other.value) + return String(self._value + other._value) __add__ = mappend @@ -57,7 +61,7 @@ def mzero(cls) -> Addition: return cls(0) def mappend(self, other: Addition) -> Addition: - return Addition(self.value + other.value) + return Addition(self._value + other._value) __add__ = mappend @@ -68,6 +72,6 @@ def mzero(cls) -> Multiplication: return cls(1) def mappend(self, other: Multiplication) -> Multiplication: - return Multiplication(self.value * other.value) + return Multiplication(self._value * other._value) __add__ = mappend diff --git a/monads/set.py b/monads/set.py index 4bb4454..498b42b 100644 --- a/monads/set.py +++ b/monads/set.py @@ -38,15 +38,15 @@ def unpack(k: T) -> set: return Set(unpack(value)) def bind(self, function: Callable[[T], Set[S]]) -> Set[S]: - return reduce(Set.mappend, map(function, self.value), Set.mzero()) + return reduce(Set.mappend, map(function, self._value), Set.mzero()) def map(self, function: Callable[[T], S]) -> Set[S]: - return Set(set(map(function, self.value))) + return Set(set(map(function, self._value))) def apply(self, functor: Set[Callable[[T], S]]) -> Set[S]: return Set( - set(chain.from_iterable([map(f, self.value) for f in functor.value])) + set(chain.from_iterable([map(f, self._value) for f in functor._value])) ) @classmethod @@ -75,7 +75,7 @@ def flat(acc: Set[T], element: T) -> Set[T]: return Set(reduce(flat, self, Set.mzero())) # type: ignore def sort(self, key: Optional[str] = None, reverse: bool = False) -> Set[T]: - lst_copy = self.value.copy() + lst_copy = self._value.copy() lst_copy.sort(key=key, reverse=reverse) # type: ignore return Set(lst_copy) @@ -86,22 +86,22 @@ def fold( functor = uncurry(cast(CurriedBinary, func)) else: functor = func - return reduce(functor, self.value, base_val) # type: ignore + return reduce(functor, self._value, base_val) # type: ignore __and__ = lambda other, self: Set.apply(self, other) # type: ignore def mappend(self, other: Set[T]) -> Set[T]: - return Set(self.value.union(other.value)) + return Set(self._value.union(other._value)) __add__ = mappend __mul__ = __rmul__ = map __rshift__ = bind def __sizeof__(self) -> int: - return self.value.__sizeof__() + return self._value.__sizeof__() def __len__(self) -> int: - return len(set(self.value)) + return len(set(self._value)) def __iter__(self) -> Iterator[T]: - return iter(self.value) + return iter(self._value) diff --git a/tests/test_monoids.py b/tests/test_monoids.py index 927a11c..78161dc 100644 --- a/tests/test_monoids.py +++ b/tests/test_monoids.py @@ -55,3 +55,11 @@ def test_mconcat(constructor: Constructor) -> None: c: Monoid = construct(constructor, 3) expected: Monoid = a.mappend(b).mappend(c) assert expected == cls.mconcat([a, b, c]) + + +def test_immutability(constructor: Constructor) -> None: + a: Monoid = construct(constructor, 1) + with pytest.raises(AttributeError) as excinfo: + # this is ignore on porpouse othewise the mypy test fail. Uncomment to check the Final check with mypy + a.value = 2 # type: ignore + assert "can't set attribute" in str(excinfo.value) From 9869a5cd4996c87bef84ebea7a2c58df30c6871e Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Thu, 31 Dec 2020 13:50:03 +0100 Subject: [PATCH 46/49] improved coverage --- tests/test_monoids.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_monoids.py b/tests/test_monoids.py index 78161dc..d444b5e 100644 --- a/tests/test_monoids.py +++ b/tests/test_monoids.py @@ -59,6 +59,7 @@ def test_mconcat(constructor: Constructor) -> None: def test_immutability(constructor: Constructor) -> None: a: Monoid = construct(constructor, 1) + assert a.value == 1 with pytest.raises(AttributeError) as excinfo: # this is ignore on porpouse othewise the mypy test fail. Uncomment to check the Final check with mypy a.value = 2 # type: ignore From 52819c35076b7c149c92e3540ae805b0beb953eb Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Thu, 31 Dec 2020 16:26:09 +0100 Subject: [PATCH 47/49] Docs and tests --- README.md | 3 +++ tests/test_monoids.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a2ae132..af15c83 100644 --- a/README.md +++ b/README.md @@ -147,6 +147,9 @@ value if the list is empty. ## Monads +Wrapped values should be immutable: they are _protected_ from accidental direct writing with *Final* type and the pythonic naming convention. + + ### Maybe[T] Represents optional data. A `Maybe` instance of a certain type `T` will diff --git a/tests/test_monoids.py b/tests/test_monoids.py index d444b5e..c5e19bb 100644 --- a/tests/test_monoids.py +++ b/tests/test_monoids.py @@ -59,7 +59,7 @@ def test_mconcat(constructor: Constructor) -> None: def test_immutability(constructor: Constructor) -> None: a: Monoid = construct(constructor, 1) - assert a.value == 1 + assert a.value != None with pytest.raises(AttributeError) as excinfo: # this is ignore on porpouse othewise the mypy test fail. Uncomment to check the Final check with mypy a.value = 2 # type: ignore From bb9d0ff22ba2592a4be4ccb18a9f7fb7e639c77b Mon Sep 17 00:00:00 2001 From: Automated Publisher Date: Thu, 31 Dec 2020 15:35:22 +0000 Subject: [PATCH 48/49] Automated publish: Thu Dec 31 15:35:22 UTC 2020 299407e375d19c8c17a6c0d8c92a6676712dd26d --- monads/__init__.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/monads/__init__.py b/monads/__init__.py index 25e257a..f14c3a0 100644 --- a/monads/__init__.py +++ b/monads/__init__.py @@ -8,4 +8,4 @@ from .future import Future from .reader import Reader -version = "v0.0.14" +version = "v0.0.15" diff --git a/setup.py b/setup.py index 9914ae7..f5a1697 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name="typesafe-monads", - version="v0.0.14", + version="v0.0.15", author="Correl Roush, Sam Reghenzi", author_email="correl@gmail.com, sammyrulez@gmail.com", description="Type-annotated monad implementations for Python 3.7+", From d046141d8d55a5a29fa8ad28637c9e14a388e721 Mon Sep 17 00:00:00 2001 From: Samuele Reghenzi Date: Sat, 2 Jan 2021 12:36:05 +0100 Subject: [PATCH 49/49] fixed derived docs --- README.md | 6 +++--- setup.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index af15c83..676361c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Type-safe Monads -![Build](https://github.com/sammyrulez/typesafe-monads/workflows/Build/badge.svg) -[![codecov](https://codecov.io/gh/sammyrulez/typesafe-monads/branch/master/graph/badge.svg)](https://codecov.io/gh/sammyrulez/typesafe-monads) +![Build](https://github.com/correl/typesafe-monads/workflows/Build/badge.svg) +[![codecov](https://codecov.io/gh/correl/typesafe-monads/branch/master/graph/badge.svg)](https://codecov.io/gh/correl/typesafe-monads) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black) This is an experiment in building monads in Python supported by strict @@ -27,7 +27,7 @@ I added some utility methods to make it easier to use in my day to day code and There is no *pipy* release ( yet) ```bash -$ pip install git+https://github.com//sammyrulez/typesafe-monads#egg=typesafe-monads-2 +$ pip install typesafe-monads ``` ## Curring diff --git a/setup.py b/setup.py index f5a1697..ba194de 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ description="Type-annotated monad implementations for Python 3.7+", long_description=long_description, long_description_content_type="text/markdown", - url="https://github.com/sammyrulez/typesafe-monads", + url="https://github.com/correl/typesafe-monads", packages=["monads"], package_data={"monads": ["py.typed"]}, include_package_data=True,