monadcontainers.monads
1from typing import Any, Callable, Generic 2from typing import List as ListType 3from typing import Optional, Type, TypeVar, Union 4 5T = TypeVar("T") 6U = TypeVar("U") 7 8 9class Monad(Generic[T]): 10 """ 11 Identity monad (also the parent class for all other monads). 12 13 No additional work is handled on function calls, apart from returning 14 a new monad with an updated result. 15 16 Can be used like so: 17 18 ```python 19 def make_loud(x:str) -> str: 20 return x.upper() 21 22 x = Monad("hello world!") >> make_loud 23 24 print(x.value) 25 # prints out "HELLO WORLD!" 26 ``` 27 """ 28 def __init__(self: Any, value: T) -> None: 29 """ 30 Initialise a monad with the given value 31 """ 32 self.value: T = value 33 34 def bind(self, func) -> "Monad": 35 """ 36 Method to apply function to value of Monad. 37 Returns a monad with the updated value. 38 39 Example: 40 ```python 41 Monad(2).bind(lambda x: x+1) == Monad(3) 42 ``` 43 44 Can be aliased with `>>` symbol: 45 ```python 46 (Monad(2) >> (lambda x: x+1)) == Monad(3) 47 """ 48 return Monad(func(self.value)) 49 50 def __rshift__(self, other) -> "Monad": 51 """ 52 Dunder method to alias bind into >> 53 """ 54 return self.bind(other) 55 56 def unwrap(self) -> T: 57 """ 58 Return only the value of the monad without wrapping 59 it. 60 61 ```python 62 Monad(4).unwrap() == 4 63 ``` 64 """ 65 return self.value 66 67 def __str__(self) -> str: 68 """ 69 String representation 70 """ 71 return f"{self.__class__.__name__}({self.value})" 72 73 def __repr__(self) -> str: 74 """ 75 For repls 76 """ 77 return self.__str__() 78 79 80class Maybe(Monad, Generic[T]): 81 """ 82 Monad to handle None values. 83 84 Write functions as if they can't recieve None values. 85 If Monad value is None, it will skip execution of function 86 and remain as None. 87 88 None example: 89 ```python 90 Maybe(None) >> (lambda x: x + 4) # will remain None 91 ``` 92 """ 93 def bind(self, func: Callable) -> "Maybe": 94 """ 95 Execute function on value, unless None 96 (in which case return None monad) 97 """ 98 if self.value is None: 99 return Maybe(None) 100 return Maybe(func(self.value)) 101 102 103class List(Monad, Generic[T]): 104 """ 105 Monad to apply function to all items in list. 106 107 Write functions as if they act on a single value. 108 109 Example: 110 ```python 111 x = ( 112 List([1, 3, 7]) 113 >> (lambda x: x + 1) 114 >> (lambda x: x / 2) 115 ) 116 # x will evaluate to List([1, 2, 4]) 117 """ 118 def __init__(self: Any, value: ListType[T]) -> None: 119 """ 120 Initialise a monad with the given list 121 """ 122 self.value: ListType[T] = value 123 124 def bind(self, func: Callable) -> "List": 125 """ 126 Map function on every element of list: 127 128 ```python 129 def make_exciting(text: str) -> str: 130 return test.upper() + "!!!" 131 132 fun_stuff = ( 133 List(["hats", "cats", "bats"]) 134 >> make_exciting 135 ).unwrap() 136 137 fun_stuff == ["HATS!!!", "CATS!!!", "BATS!!!"] 138 """ 139 return List([func(x) for x in self.value]) 140 141 def filter(self, func: Callable) -> "List": 142 """ 143 Filter list to only elements with true return: 144 145 ```python 146 x = ( 147 List([1, 2, 3, 4]) 148 .filter(lambda x: x % 2 == 0) 149 ).unwrap() 150 151 x == [2, 4] 152 """ 153 return List([i for i in self.value if func(i)]) 154 155 def unwrap(self) -> ListType[T]: 156 """ 157 Return only the value of the monad without wrapping 158 it. 159 160 ```python 161 List([4]).unwrap() == [4] 162 ``` 163 """ 164 return self.value 165 166 167class Result(Monad, Generic[T]): 168 """ 169 Monad to handle errors. Handle exceptions on unwrap: 170 171 ```python 172 x = ( 173 Result(3) 174 >> (lamba x / 0) 175 ) 176 177 x.value == None 178 isinstance(x.exception, ZeroDivisionError) 179 x.unwrap_or(4) == 4 180 ``` 181 """ 182 def __init__(self, value: T, exception: Optional[Type[Exception]] = None): 183 """ 184 Create Result monad with value and exception 185 (one of which will always be None) 186 """ 187 self.value: T = value 188 self.exception = exception 189 190 def bind(self, func): 191 """ 192 Execute function on value, if error is raised, 193 returned Monad will have value of "None" and an exception. 194 195 Otherwise, exception will be None, and value will be return. 196 197 If exception already exists, function won't be executed. 198 """ 199 if self.exception: 200 return self 201 try: 202 return Result(func(self.value)) 203 except Exception as exception: 204 return Result(None, exception) 205 206 def unwrap(self) -> T: 207 """ 208 If exception is raised, will raise exception. 209 Otherwise will evaluate to value. 210 """ 211 if self.exception: 212 raise self.exception 213 return self.value 214 215 def unwrap_or(self, value: U) -> Union[T, U]: 216 """ 217 If exception is raised, will default to given value. 218 """ 219 if self.exception: 220 return value 221 return self.value 222 223 def __str__(self) -> str: 224 """ 225 Custom string representation 226 """ 227 return f"{self.__class__.__name__}({self.exception or self.value})"
10class Monad(Generic[T]): 11 """ 12 Identity monad (also the parent class for all other monads). 13 14 No additional work is handled on function calls, apart from returning 15 a new monad with an updated result. 16 17 Can be used like so: 18 19 ```python 20 def make_loud(x:str) -> str: 21 return x.upper() 22 23 x = Monad("hello world!") >> make_loud 24 25 print(x.value) 26 # prints out "HELLO WORLD!" 27 ``` 28 """ 29 def __init__(self: Any, value: T) -> None: 30 """ 31 Initialise a monad with the given value 32 """ 33 self.value: T = value 34 35 def bind(self, func) -> "Monad": 36 """ 37 Method to apply function to value of Monad. 38 Returns a monad with the updated value. 39 40 Example: 41 ```python 42 Monad(2).bind(lambda x: x+1) == Monad(3) 43 ``` 44 45 Can be aliased with `>>` symbol: 46 ```python 47 (Monad(2) >> (lambda x: x+1)) == Monad(3) 48 """ 49 return Monad(func(self.value)) 50 51 def __rshift__(self, other) -> "Monad": 52 """ 53 Dunder method to alias bind into >> 54 """ 55 return self.bind(other) 56 57 def unwrap(self) -> T: 58 """ 59 Return only the value of the monad without wrapping 60 it. 61 62 ```python 63 Monad(4).unwrap() == 4 64 ``` 65 """ 66 return self.value 67 68 def __str__(self) -> str: 69 """ 70 String representation 71 """ 72 return f"{self.__class__.__name__}({self.value})" 73 74 def __repr__(self) -> str: 75 """ 76 For repls 77 """ 78 return self.__str__()
Identity monad (also the parent class for all other monads).
No additional work is handled on function calls, apart from returning a new monad with an updated result.
Can be used like so:
def make_loud(x:str) -> str:
return x.upper()
x = Monad("hello world!") >> make_loud
print(x.value)
# prints out "HELLO WORLD!"
29 def __init__(self: Any, value: T) -> None: 30 """ 31 Initialise a monad with the given value 32 """ 33 self.value: T = value
Initialise a monad with the given value
35 def bind(self, func) -> "Monad": 36 """ 37 Method to apply function to value of Monad. 38 Returns a monad with the updated value. 39 40 Example: 41 ```python 42 Monad(2).bind(lambda x: x+1) == Monad(3) 43 ``` 44 45 Can be aliased with `>>` symbol: 46 ```python 47 (Monad(2) >> (lambda x: x+1)) == Monad(3) 48 """ 49 return Monad(func(self.value))
Method to apply function to value of Monad. Returns a monad with the updated value.
Example:
Monad(2).bind(lambda x: x+1) == Monad(3)
Can be aliased with >>
symbol:
```python
(Monad(2) >> (lambda x: x+1)) == Monad(3)
81class Maybe(Monad, Generic[T]): 82 """ 83 Monad to handle None values. 84 85 Write functions as if they can't recieve None values. 86 If Monad value is None, it will skip execution of function 87 and remain as None. 88 89 None example: 90 ```python 91 Maybe(None) >> (lambda x: x + 4) # will remain None 92 ``` 93 """ 94 def bind(self, func: Callable) -> "Maybe": 95 """ 96 Execute function on value, unless None 97 (in which case return None monad) 98 """ 99 if self.value is None: 100 return Maybe(None) 101 return Maybe(func(self.value))
Monad to handle None values.
Write functions as if they can't recieve None values. If Monad value is None, it will skip execution of function and remain as None.
None example:
Maybe(None) >> (lambda x: x + 4) # will remain None
104class List(Monad, Generic[T]): 105 """ 106 Monad to apply function to all items in list. 107 108 Write functions as if they act on a single value. 109 110 Example: 111 ```python 112 x = ( 113 List([1, 3, 7]) 114 >> (lambda x: x + 1) 115 >> (lambda x: x / 2) 116 ) 117 # x will evaluate to List([1, 2, 4]) 118 """ 119 def __init__(self: Any, value: ListType[T]) -> None: 120 """ 121 Initialise a monad with the given list 122 """ 123 self.value: ListType[T] = value 124 125 def bind(self, func: Callable) -> "List": 126 """ 127 Map function on every element of list: 128 129 ```python 130 def make_exciting(text: str) -> str: 131 return test.upper() + "!!!" 132 133 fun_stuff = ( 134 List(["hats", "cats", "bats"]) 135 >> make_exciting 136 ).unwrap() 137 138 fun_stuff == ["HATS!!!", "CATS!!!", "BATS!!!"] 139 """ 140 return List([func(x) for x in self.value]) 141 142 def filter(self, func: Callable) -> "List": 143 """ 144 Filter list to only elements with true return: 145 146 ```python 147 x = ( 148 List([1, 2, 3, 4]) 149 .filter(lambda x: x % 2 == 0) 150 ).unwrap() 151 152 x == [2, 4] 153 """ 154 return List([i for i in self.value if func(i)]) 155 156 def unwrap(self) -> ListType[T]: 157 """ 158 Return only the value of the monad without wrapping 159 it. 160 161 ```python 162 List([4]).unwrap() == [4] 163 ``` 164 """ 165 return self.value
Monad to apply function to all items in list.
Write functions as if they act on a single value.
Example: ```python x = ( List([1, 3, 7])
(lambda x: x + 1) (lambda x: x / 2) )
x will evaluate to List([1, 2, 4])
142 def filter(self, func: Callable) -> "List": 143 """ 144 Filter list to only elements with true return: 145 146 ```python 147 x = ( 148 List([1, 2, 3, 4]) 149 .filter(lambda x: x % 2 == 0) 150 ).unwrap() 151 152 x == [2, 4] 153 """ 154 return List([i for i in self.value if func(i)])
Filter list to only elements with true return:
```python x = ( List([1, 2, 3, 4]) .filter(lambda x: x % 2 == 0) ).unwrap()
x == [2, 4]
168class Result(Monad, Generic[T]): 169 """ 170 Monad to handle errors. Handle exceptions on unwrap: 171 172 ```python 173 x = ( 174 Result(3) 175 >> (lamba x / 0) 176 ) 177 178 x.value == None 179 isinstance(x.exception, ZeroDivisionError) 180 x.unwrap_or(4) == 4 181 ``` 182 """ 183 def __init__(self, value: T, exception: Optional[Type[Exception]] = None): 184 """ 185 Create Result monad with value and exception 186 (one of which will always be None) 187 """ 188 self.value: T = value 189 self.exception = exception 190 191 def bind(self, func): 192 """ 193 Execute function on value, if error is raised, 194 returned Monad will have value of "None" and an exception. 195 196 Otherwise, exception will be None, and value will be return. 197 198 If exception already exists, function won't be executed. 199 """ 200 if self.exception: 201 return self 202 try: 203 return Result(func(self.value)) 204 except Exception as exception: 205 return Result(None, exception) 206 207 def unwrap(self) -> T: 208 """ 209 If exception is raised, will raise exception. 210 Otherwise will evaluate to value. 211 """ 212 if self.exception: 213 raise self.exception 214 return self.value 215 216 def unwrap_or(self, value: U) -> Union[T, U]: 217 """ 218 If exception is raised, will default to given value. 219 """ 220 if self.exception: 221 return value 222 return self.value 223 224 def __str__(self) -> str: 225 """ 226 Custom string representation 227 """ 228 return f"{self.__class__.__name__}({self.exception or self.value})"
Monad to handle errors. Handle exceptions on unwrap:
x = (
Result(3)
>> (lamba x / 0)
)
x.value == None
isinstance(x.exception, ZeroDivisionError)
x.unwrap_or(4) == 4