1
0
Fork 0
mirror of https://github.com/correl/typesafe-monads.git synced 2025-04-05 09:12:36 -09:00
This commit is contained in:
Sam Reghenzi 2021-01-02 06:37:24 -05:00 committed by GitHub
commit 89d2d46ae5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 552 additions and 73 deletions

76
.github/workflows/build.yaml vendored Normal file
View file

@ -0,0 +1,76 @@
on:
push:
branches:
- master
pull_request:
branches:
- master
name: Build
jobs:
checking:
name: Lint & Test
runs-on: ubuntu-latest
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: |
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
with:
file: ./reports/coverage.xml
flags: unittests
fail_ci_if_error: true
verbose: true
releasing:
name: Build Release
runs-on: ubuntu-latest
needs: checking
if: github.ref == 'refs/heads/master'
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
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
bump_version_scheme: patch
- 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
black setup.py
black monads/__init__.py
- uses: mikeal/publish-to-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

22
.github/workflows/todo.yaml vendored Normal file
View file

@ -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"

1
.gitignore vendored
View file

@ -109,3 +109,4 @@ venv.bak/
.mypy_cache/
.dmypy.json
dmypy.json
.vscode

View file

@ -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

View file

@ -1,6 +1,6 @@
# Type-safe Monads
[![Build Status](https://travis-ci.com/correl/typesafe-monads.svg?branch=master)](https://travis-ci.com/correl/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)
@ -18,6 +18,31 @@ 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/)
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 typesafe-monads
```
## 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
@ -122,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
@ -146,7 +174,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`.
@ -159,3 +193,29 @@ Represents an asynchronous action.
### Reader[T]
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, 9])
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*

View file

@ -2,7 +2,10 @@ from .functor import Functor
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
from .reader import Reader
version = "v0.0.15"

View file

@ -19,4 +19,5 @@ class Applicative(Functor[T]):
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)

View file

@ -54,12 +54,15 @@ class Future(Monad[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)
__and__ = lambda other, self: Future.apply(self, other) # type: ignore
__mul__ = __rmul__ = map

View file

@ -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")
@ -16,16 +27,21 @@ class List(Monad[T], Monoidal[list]):
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
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 +49,48 @@ class List(Monad[T], Monoidal[list]):
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([])
empty: List[_List[T]] = List.pure([])
return reduce(mcons, xs, empty)
@classmethod
def mzero(cls) -> List[T]:
return cls(list())
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]:
return List(self.value + other.value)
return List(self._value + other._value)
__add__ = mappend
__and__ = lambda other, self: List.apply(self, other)
__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)

View file

@ -1,6 +1,18 @@
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,
Sized,
TypeVar,
Union,
cast,
)
from . import result
from .monad import Monad
from .monoid import Monoid
@ -10,7 +22,7 @@ S = TypeVar("S")
E = TypeVar("E")
class Maybe(Monad[T]):
class Maybe(Monad[T], Iterable, Sized):
def __init__(self) -> None: # pragma: no cover
raise NotImplementedError
@ -39,6 +51,12 @@ class Maybe(Monad[T]):
new: Maybe[S] = Nothing()
return new
def or_else(self, default: T) -> T: # pragma: no cover
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]]:
"""Evaluate monadic actions in sequence, collecting results."""
@ -46,7 +64,8 @@ class Maybe(Monad[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 +104,9 @@ class Maybe(Monad[T]):
else:
return Nothing()
__and__ = lambda other, self: Maybe.apply(self, other) # type: ignore
__rshift__ = bind
__and__ = lambda other, self: Maybe.apply(self, other)
__mul__ = __rmul__ = map
@ -100,6 +120,23 @@ class Just(Maybe[T]):
def __repr__(self) -> str: # pragma: no cover
return f"<Just {self.value}>"
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:
@ -111,6 +148,19 @@ class Nothing(Maybe[T]):
def __repr__(self) -> str: # pragma: no cover
return "<Nothing>"
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)
@ -126,19 +176,19 @@ class First(Monoid[Maybe[T]]):
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"<First {self.value}>"
return f"<First {self._value}>"
__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]]):
@ -147,16 +197,16 @@ class Last(Monoid[Maybe[T]]):
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"<Last {self.value}>"
return f"<Last {self._value}>"
__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

View file

@ -2,14 +2,14 @@ from __future__ import annotations
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 @@ class Monoid(Generic[T]):
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 @@ class String(Monoidal[str]):
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 @@ class Addition(Monoidal[Union[int, float]]):
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 @@ class Multiplication(Monoidal[Union[int, float]]):
return cls(1)
def mappend(self, other: Multiplication) -> Multiplication:
return Multiplication(self.value * other.value)
return Multiplication(self._value * other._value)
__add__ = mappend

View file

@ -10,7 +10,7 @@ S = TypeVar("S")
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)
@ -58,7 +58,8 @@ class Reader(Monad[T], Generic[Env, 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 @@ class Reader(Monad[T], Generic[Env, T]):
signature = inspect.signature(self)
return f"<Reader {module}.{name}{signature}>"
__and__ = lambda other, self: Reader.apply(self, other) # type: ignore
__mul__ = __rmul__ = map
__rshift__ = bind
__and__ = lambda other, self: Reader.apply(self, other)

View file

@ -60,7 +60,7 @@ class Result(Monad[T], Generic[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 @@ class Result(Monad[T], Generic[T, E]):
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

107
monads/set.py Normal file
View file

@ -0,0 +1,107 @@
from __future__ import annotations
from functools import reduce
from itertools import chain
from monads import functor, List
from typing import (
Any,
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]:
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())
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)

View file

@ -9,5 +9,6 @@ exclude = '''
| \.mypy_cache
| build
| dist
| env
)/
'''

View file

@ -3,3 +3,6 @@ test=pytest
[tool:pytest]
addopts = --black --mypy --cov monads --cov-report xml:reports/coverage.xml
[mypy-tests]
ignore_errors = True

View file

@ -6,9 +6,9 @@ with open("README.md", "r") as f:
setup(
name="typesafe-monads",
version="0.8",
author="Correl Roush",
author_email="correl@gmail.com",
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+",
long_description=long_description,
long_description_content_type="text/markdown",

View file

@ -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

View file

@ -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

View file

@ -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))
)

37
tests/test_list.py Normal file
View file

@ -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()

View file

@ -126,3 +126,35 @@ def test_just_to_optional() -> None:
def test_nothing_to_optional() -> None:
assert None == Nothing().toOptional()
def test_as_iterable() -> None:
m_empty: Maybe[str] = Nothing()
i = 0
for n in m_empty:
i = i + 1
assert i == 0
for n in Just("one"):
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()
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"
m_full: Maybe[Maybe[str]] = Just(Just("becon"))
assert m_full.flatten().or_else("backup") == "becon"

View file

@ -55,3 +55,12 @@ 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)
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
assert "can't set attribute" in str(excinfo.value)

37
tests/test_set.py Normal file
View file

@ -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()