mirror of
https://github.com/correl/typesafe-monads.git
synced 2024-11-22 03:00:16 +00:00
Merge pull request #7 from sammyrulez/Imutable
add some immutability safety
This commit is contained in:
commit
299407e375
6 changed files with 47 additions and 31 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue