Merge pull request #7 from sammyrulez/Imutable

add some immutability safety
This commit is contained in:
Sam Reghenzi 2020-12-31 16:33:02 +01:00 committed by GitHub
commit 299407e375
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 47 additions and 31 deletions

View file

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

View file

@ -27,15 +27,15 @@ 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
@ -64,7 +64,7 @@ class List(Monad[T], Monoidal[list]):
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 @@ class List(Monad[T], Monoidal[list]):
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)

View file

@ -176,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]]):
@ -197,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

@ -38,15 +38,15 @@ class Set(Monad[T], Monoidal[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 @@ class Set(Monad[T], Monoidal[set]):
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 @@ class Set(Monad[T], Monoidal[set]):
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)

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)