pythonic-fp.containers 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythonic_fp/containers/__init__.py +19 -0
- pythonic_fp/containers/box.py +159 -0
- pythonic_fp/containers/functional_tuple.py +189 -0
- pythonic_fp/containers/immutable_list.py +222 -0
- pythonic_fp/containers/maybe.py +226 -0
- pythonic_fp/containers/py.typed +0 -0
- pythonic_fp/containers/xor.py +314 -0
- pythonic_fp_containers-2.0.0.dist-info/METADATA +50 -0
- pythonic_fp_containers-2.0.0.dist-info/RECORD +11 -0
- pythonic_fp_containers-2.0.0.dist-info/WHEEL +4 -0
- pythonic_fp_containers-2.0.0.dist-info/licenses/LICENSE +190 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# Copyright 2023-2025 Geoffrey R. Scheller
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Pythonic FP - Maybe Monad"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
__all__ = ['MayBe']
|
|
20
|
+
|
|
21
|
+
from collections.abc import Callable, Iterator, Sequence
|
|
22
|
+
from typing import cast, Final, Never, overload, TypeVar
|
|
23
|
+
from pythonic_fp.fptools.singletons import Sentinel
|
|
24
|
+
|
|
25
|
+
D = TypeVar('D', covariant=True)
|
|
26
|
+
|
|
27
|
+
class MayBe[D]:
|
|
28
|
+
"""Maybe monad, data structure wrapping a potentially missing value.
|
|
29
|
+
|
|
30
|
+
- where ``MayBe(value)`` contains a possible value of type ``+D``
|
|
31
|
+
- ``MayBe()`` represent a non-existent or missing value of type ``+D``
|
|
32
|
+
- immutable semantics
|
|
33
|
+
|
|
34
|
+
- immutable, therefore made covariant
|
|
35
|
+
|
|
36
|
+
- can store any item of any type, including ``None``
|
|
37
|
+
- can store any value of any type with one exception
|
|
38
|
+
|
|
39
|
+
:: warning:
|
|
40
|
+
hashability invalidated if contained value is not hashable
|
|
41
|
+
|
|
42
|
+
:: note:
|
|
43
|
+
MayBe(): MayBe[+D] -> mb: Xor[+D]
|
|
44
|
+
MayBe(value: +D) -> mb: Xor[+D]
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
U = TypeVar('U', covariant=True)
|
|
48
|
+
V = TypeVar('V', covariant=True)
|
|
49
|
+
T = TypeVar('T')
|
|
50
|
+
|
|
51
|
+
__slots__ = ('_value',)
|
|
52
|
+
__match_args__ = ('_value',)
|
|
53
|
+
|
|
54
|
+
@overload
|
|
55
|
+
def __init__(self) -> None: ...
|
|
56
|
+
@overload
|
|
57
|
+
def __init__(self, value: D) -> None: ...
|
|
58
|
+
|
|
59
|
+
def __init__(self, value: D | Sentinel = Sentinel('MayBe')) -> None:
|
|
60
|
+
self._value: D | Sentinel = value
|
|
61
|
+
|
|
62
|
+
def __hash__(self) -> int:
|
|
63
|
+
return hash((Sentinel('MayBe'), self._value))
|
|
64
|
+
|
|
65
|
+
def __bool__(self) -> bool:
|
|
66
|
+
return self._value is not Sentinel('MayBe')
|
|
67
|
+
|
|
68
|
+
def __iter__(self) -> Iterator[D]:
|
|
69
|
+
if self:
|
|
70
|
+
yield cast(D, self._value)
|
|
71
|
+
|
|
72
|
+
def __repr__(self) -> str:
|
|
73
|
+
if self:
|
|
74
|
+
return 'MayBe(' + repr(self._value) + ')'
|
|
75
|
+
return 'MayBe()'
|
|
76
|
+
|
|
77
|
+
def __len__(self) -> int:
|
|
78
|
+
return 1 if self else 0
|
|
79
|
+
|
|
80
|
+
def __eq__(self, other: object) -> bool:
|
|
81
|
+
if not isinstance(other, type(self)):
|
|
82
|
+
return False
|
|
83
|
+
if self._value is other._value:
|
|
84
|
+
return True
|
|
85
|
+
if self._value == other._value:
|
|
86
|
+
return True
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
@overload
|
|
90
|
+
def get(self) -> D | Never: ...
|
|
91
|
+
@overload
|
|
92
|
+
def get(self, alt: D) -> D: ...
|
|
93
|
+
|
|
94
|
+
def get(self, alt: D | Sentinel = Sentinel('MayBe')) -> D | Never:
|
|
95
|
+
"""Return the contained value if it exists, otherwise an alternate value.
|
|
96
|
+
|
|
97
|
+
:: warning:
|
|
98
|
+
Unsafe method ``get``. Will raise ``ValueError`` if MayBe empty
|
|
99
|
+
and an alt return value not given. Best practice is to first check
|
|
100
|
+
the MayBe in a boolean context.
|
|
101
|
+
|
|
102
|
+
:rtype: +D
|
|
103
|
+
:raises ValueError: when an alternate value is not provided but needed
|
|
104
|
+
"""
|
|
105
|
+
_sentinel: Final[Sentinel] = Sentinel('MayBe')
|
|
106
|
+
if self._value is not _sentinel:
|
|
107
|
+
return cast(D, self._value)
|
|
108
|
+
if alt is _sentinel:
|
|
109
|
+
msg = 'MayBe: an alternate return type not provided'
|
|
110
|
+
raise ValueError(msg)
|
|
111
|
+
return cast(D, alt)
|
|
112
|
+
|
|
113
|
+
def map[U](self, f: Callable[[D], U]) -> MayBe[U]:
|
|
114
|
+
"""Map function `f` over contents."""
|
|
115
|
+
|
|
116
|
+
if self:
|
|
117
|
+
return MayBe(f(cast(D, self._value)))
|
|
118
|
+
return cast(MayBe[U], self)
|
|
119
|
+
|
|
120
|
+
def bind[U](self, f: Callable[[D], MayBe[U]]) -> MayBe[U]:
|
|
121
|
+
"""Flatmap ``MayBe`` with function ``f``."""
|
|
122
|
+
return f(cast(D, self._value)) if self else cast(MayBe[U], self)
|
|
123
|
+
|
|
124
|
+
def map_except[U](self, f: Callable[[D], U]) -> MayBe[U]:
|
|
125
|
+
"""Map function ``f`` over contents.
|
|
126
|
+
|
|
127
|
+
If ``f`` should fail, return a ``MayBe()``.
|
|
128
|
+
|
|
129
|
+
:: warning:
|
|
130
|
+
Swallows exceptions
|
|
131
|
+
"""
|
|
132
|
+
if not self:
|
|
133
|
+
return cast(MayBe[U], self)
|
|
134
|
+
try:
|
|
135
|
+
return MayBe(f(cast(D, self._value)))
|
|
136
|
+
except (
|
|
137
|
+
LookupError,
|
|
138
|
+
ValueError,
|
|
139
|
+
TypeError,
|
|
140
|
+
BufferError,
|
|
141
|
+
ArithmeticError,
|
|
142
|
+
RecursionError,
|
|
143
|
+
ReferenceError,
|
|
144
|
+
RuntimeError,
|
|
145
|
+
):
|
|
146
|
+
return MayBe()
|
|
147
|
+
|
|
148
|
+
def bind_except[U](self, f: Callable[[D], MayBe[U]]) -> MayBe[U]:
|
|
149
|
+
"""Flatmap `MayBe` with function `f`.
|
|
150
|
+
|
|
151
|
+
:: warning:
|
|
152
|
+
Swallows exceptions
|
|
153
|
+
"""
|
|
154
|
+
try:
|
|
155
|
+
return f(cast(D, self._value)) if self else cast(MayBe[U], self)
|
|
156
|
+
except (
|
|
157
|
+
LookupError,
|
|
158
|
+
ValueError,
|
|
159
|
+
TypeError,
|
|
160
|
+
BufferError,
|
|
161
|
+
ArithmeticError,
|
|
162
|
+
RecursionError,
|
|
163
|
+
ReferenceError,
|
|
164
|
+
RuntimeError,
|
|
165
|
+
):
|
|
166
|
+
return MayBe()
|
|
167
|
+
|
|
168
|
+
@staticmethod
|
|
169
|
+
def sequence[U](sequence_mb_u: Sequence[MayBe[U]]) -> MayBe[Sequence[U]]:
|
|
170
|
+
"""Sequence a mutable indexable of type ``MayBe[~U]``
|
|
171
|
+
|
|
172
|
+
If the iterated `MayBe` values are not all empty,
|
|
173
|
+
|
|
174
|
+
- return a `MayBe` of the Sequence subtype of the contained values
|
|
175
|
+
- otherwise return an empty `MayBe`
|
|
176
|
+
|
|
177
|
+
"""
|
|
178
|
+
list_items: list[U] = list()
|
|
179
|
+
|
|
180
|
+
for mb_u in sequence_mb_u:
|
|
181
|
+
if mb_u:
|
|
182
|
+
list_items.append(mb_u.get())
|
|
183
|
+
else:
|
|
184
|
+
return MayBe()
|
|
185
|
+
|
|
186
|
+
sequence_type = cast(Sequence[U], type(sequence_mb_u))
|
|
187
|
+
|
|
188
|
+
return MayBe(sequence_type(list_items)) # type: ignore # subclass will be callable
|
|
189
|
+
|
|
190
|
+
@staticmethod
|
|
191
|
+
def failable_call[T, V](f: Callable[[T], V], t: T) -> MayBe[V]:
|
|
192
|
+
"""Return MayBe wrapped result of a function call that can fail."""
|
|
193
|
+
try:
|
|
194
|
+
mb_return = MayBe(f(t))
|
|
195
|
+
except (
|
|
196
|
+
LookupError,
|
|
197
|
+
ValueError,
|
|
198
|
+
TypeError,
|
|
199
|
+
BufferError,
|
|
200
|
+
ArithmeticError,
|
|
201
|
+
RecursionError,
|
|
202
|
+
ReferenceError,
|
|
203
|
+
RuntimeError,
|
|
204
|
+
):
|
|
205
|
+
mb_return = MayBe()
|
|
206
|
+
|
|
207
|
+
return mb_return
|
|
208
|
+
|
|
209
|
+
@staticmethod
|
|
210
|
+
def failable_index[V](vs: Sequence[V], ii: int) -> MayBe[V]:
|
|
211
|
+
"""Return a MayBe of an indexed value that can fail."""
|
|
212
|
+
try:
|
|
213
|
+
mb_return = MayBe(vs[ii])
|
|
214
|
+
except (
|
|
215
|
+
LookupError,
|
|
216
|
+
ValueError,
|
|
217
|
+
TypeError,
|
|
218
|
+
BufferError,
|
|
219
|
+
ArithmeticError,
|
|
220
|
+
RecursionError,
|
|
221
|
+
ReferenceError,
|
|
222
|
+
RuntimeError,
|
|
223
|
+
):
|
|
224
|
+
mb_return = MayBe()
|
|
225
|
+
|
|
226
|
+
return mb_return
|
|
File without changes
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# Copyright 2023-2025 Geoffrey R. Scheller
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Pythonic FP - Either monad
|
|
16
|
+
|
|
17
|
+
- *class* Xor: left biased either monad
|
|
18
|
+
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
__all__ = ['Xor', 'LEFT', 'RIGHT']
|
|
24
|
+
|
|
25
|
+
from collections.abc import Callable, Iterator, Sequence
|
|
26
|
+
from typing import cast, Never, overload, TypeVar
|
|
27
|
+
from pythonic_fp.fptools.bool import Bool as Both, Truth as Left, Lie as Right
|
|
28
|
+
from pythonic_fp.fptools.singletons import Sentinel as _Sentinel
|
|
29
|
+
from .maybe import MayBe
|
|
30
|
+
|
|
31
|
+
L = TypeVar('L', covariant=True)
|
|
32
|
+
R = TypeVar('R', covariant=True)
|
|
33
|
+
|
|
34
|
+
LEFT = Left('LEFT')
|
|
35
|
+
RIGHT = Right('RIGHT')
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Xor[L, R]:
|
|
39
|
+
"""Either monad, data structure semantically containing either a left
|
|
40
|
+
or a right value, but not both.
|
|
41
|
+
|
|
42
|
+
Implements a left biased Either Monad.
|
|
43
|
+
|
|
44
|
+
- `Xor(value: +L, LEFT)` produces a left `Xor`
|
|
45
|
+
- `Xor(value: +L, RIGHT)` produces a right `Xor`
|
|
46
|
+
|
|
47
|
+
In a Boolean context
|
|
48
|
+
|
|
49
|
+
- `True` if a left `Xor`
|
|
50
|
+
- `False` if a right `Xor`
|
|
51
|
+
|
|
52
|
+
Two `Xor` objects compare as equal when
|
|
53
|
+
|
|
54
|
+
- both are left values or both are right values whose values
|
|
55
|
+
|
|
56
|
+
- are the same object
|
|
57
|
+
- compare as equal
|
|
58
|
+
|
|
59
|
+
Immutable, an `Xor` does not change after being created.
|
|
60
|
+
|
|
61
|
+
- immutable semantics, map & bind return new instances
|
|
62
|
+
|
|
63
|
+
- warning: contained value need not be immutable
|
|
64
|
+
- warning: not hashable if value is mutable
|
|
65
|
+
|
|
66
|
+
:: Note:
|
|
67
|
+
Xor(value: +L, side: Left): Xor[+L, +R] -> left: Xor[+L, +R]
|
|
68
|
+
Xor(value: +R, side: Right): Xor[+L, +R] -> right: Xor[+L, +R]
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
__slots__ = '_value', '_side'
|
|
72
|
+
__match_args__ = ('_value', '_side')
|
|
73
|
+
|
|
74
|
+
U = TypeVar('U', covariant=True)
|
|
75
|
+
V = TypeVar('V', covariant=True)
|
|
76
|
+
T = TypeVar('T')
|
|
77
|
+
|
|
78
|
+
@overload
|
|
79
|
+
def __init__(self, value: L, side: Left) -> None: ...
|
|
80
|
+
@overload
|
|
81
|
+
def __init__(self, value: R, side: Right) -> None: ...
|
|
82
|
+
|
|
83
|
+
def __init__(self, value: L | R, side: Both = LEFT) -> None:
|
|
84
|
+
self._value = value
|
|
85
|
+
self._side = side
|
|
86
|
+
|
|
87
|
+
def __hash__(self) -> int:
|
|
88
|
+
return hash((_Sentinel('XOR'), self._value, self._side))
|
|
89
|
+
|
|
90
|
+
def __bool__(self) -> bool:
|
|
91
|
+
return self._side == LEFT
|
|
92
|
+
|
|
93
|
+
def __iter__(self) -> Iterator[L]:
|
|
94
|
+
if self:
|
|
95
|
+
yield cast(L, self._value)
|
|
96
|
+
|
|
97
|
+
def __repr__(self) -> str:
|
|
98
|
+
if self:
|
|
99
|
+
return 'Xor(' + repr(self._value) + ', LEFT)'
|
|
100
|
+
return 'Xor(' + repr(self._value) + ', RIGHT)'
|
|
101
|
+
|
|
102
|
+
def __str__(self) -> str:
|
|
103
|
+
if self:
|
|
104
|
+
return '< ' + str(self._value) + ' | >'
|
|
105
|
+
return '< | ' + str(self._value) + ' >'
|
|
106
|
+
|
|
107
|
+
def __len__(self) -> int:
|
|
108
|
+
# An Xor always contains just one value.
|
|
109
|
+
return 1
|
|
110
|
+
|
|
111
|
+
def __eq__(self, other: object) -> bool:
|
|
112
|
+
if not isinstance(other, type(self)):
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
if self and other:
|
|
116
|
+
if (self._value is other._value) or (self._value == other._value):
|
|
117
|
+
return True
|
|
118
|
+
|
|
119
|
+
if not self and not other:
|
|
120
|
+
if (self._value is other._value) or (self._value == other._value):
|
|
121
|
+
return True
|
|
122
|
+
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
def get(self) -> L | Never:
|
|
126
|
+
"""Get value if a left.
|
|
127
|
+
|
|
128
|
+
:: warning:
|
|
129
|
+
Unsafe method ``get``. Will raise ``ValueError`` if ``Xor``
|
|
130
|
+
is a right. Best practice is to first check the ``Xor`` in
|
|
131
|
+
a boolean context.
|
|
132
|
+
|
|
133
|
+
:return: its value if a Left
|
|
134
|
+
:rtype: +L
|
|
135
|
+
:raises ValueError: if not a left
|
|
136
|
+
"""
|
|
137
|
+
if self._side == RIGHT:
|
|
138
|
+
msg = 'Xor: get method called on a right valued Xor'
|
|
139
|
+
raise ValueError(msg)
|
|
140
|
+
return cast(L, self._value)
|
|
141
|
+
|
|
142
|
+
def get_left(self) -> MayBe[L]:
|
|
143
|
+
"""Get value of `Xor` if a left. Safer version of `get` method.
|
|
144
|
+
|
|
145
|
+
- if `Xor` contains a left value, return it wrapped in a MayBe
|
|
146
|
+
- if `Xor` contains a right value, return MayBe()
|
|
147
|
+
"""
|
|
148
|
+
if self._side == LEFT:
|
|
149
|
+
return MayBe(cast(L, self._value))
|
|
150
|
+
return MayBe()
|
|
151
|
+
|
|
152
|
+
def get_right(self) -> MayBe[R]:
|
|
153
|
+
"""Get value of `Xor` if a right
|
|
154
|
+
|
|
155
|
+
- if `Xor` contains a right value, return it wrapped in a MayBe
|
|
156
|
+
- if `Xor` contains a left value, return MayBe()
|
|
157
|
+
"""
|
|
158
|
+
if self._side == RIGHT:
|
|
159
|
+
return MayBe(cast(R, self._value))
|
|
160
|
+
return MayBe()
|
|
161
|
+
|
|
162
|
+
def map_right[V](self, f: Callable[[R], V]) -> Xor[L, V]:
|
|
163
|
+
"""Construct new Xor with a different right."""
|
|
164
|
+
if self._side == LEFT:
|
|
165
|
+
return cast(Xor[L, V], self)
|
|
166
|
+
return Xor[L, V](f(cast(R, self._value)), RIGHT)
|
|
167
|
+
|
|
168
|
+
def map[U](self, f: Callable[[L], U]) -> Xor[U, R]:
|
|
169
|
+
"""Map over if a left value. Return new instance."""
|
|
170
|
+
if self._side == RIGHT:
|
|
171
|
+
return cast(Xor[U, R], self)
|
|
172
|
+
return Xor(f(cast(L, self._value)), LEFT)
|
|
173
|
+
|
|
174
|
+
def bind[U](self, f: Callable[[L], Xor[U, R]]) -> Xor[U, R]:
|
|
175
|
+
"""Flatmap over the left value, propagate right values."""
|
|
176
|
+
if self:
|
|
177
|
+
return f(cast(L, self._value))
|
|
178
|
+
return cast(Xor[U, R], self)
|
|
179
|
+
|
|
180
|
+
def map_except[U](self, f: Callable[[L], U], fallback_right: R) -> Xor[U, R]:
|
|
181
|
+
"""Map over if a left value - with fallback upon exception.
|
|
182
|
+
|
|
183
|
+
- if `Xor` is a left then map `f` over its value
|
|
184
|
+
|
|
185
|
+
- if `f` successful return a left `Xor[+U, +R]`
|
|
186
|
+
- if `f` unsuccessful return right `Xor[+U, +R]`
|
|
187
|
+
|
|
188
|
+
- swallows many exceptions `f` may throw at run time
|
|
189
|
+
|
|
190
|
+
- if `Xor` is a right
|
|
191
|
+
|
|
192
|
+
- return new `Xor(right=self._right): Xor[+U, +R]`
|
|
193
|
+
|
|
194
|
+
"""
|
|
195
|
+
if self._side == RIGHT:
|
|
196
|
+
return cast(Xor[U, R], self)
|
|
197
|
+
|
|
198
|
+
applied: MayBe[Xor[U, R]] = MayBe()
|
|
199
|
+
fall_back: MayBe[Xor[U, R]] = MayBe()
|
|
200
|
+
try:
|
|
201
|
+
applied = MayBe(Xor(f(cast(L, self._value)), LEFT))
|
|
202
|
+
except (
|
|
203
|
+
LookupError,
|
|
204
|
+
ValueError,
|
|
205
|
+
TypeError,
|
|
206
|
+
BufferError,
|
|
207
|
+
ArithmeticError,
|
|
208
|
+
RecursionError,
|
|
209
|
+
ReferenceError,
|
|
210
|
+
RuntimeError,
|
|
211
|
+
):
|
|
212
|
+
fall_back = MayBe(cast(Xor[U, R], Xor(fallback_right, RIGHT)))
|
|
213
|
+
|
|
214
|
+
if fall_back:
|
|
215
|
+
return fall_back.get()
|
|
216
|
+
return applied.get()
|
|
217
|
+
|
|
218
|
+
def bind_except[U](
|
|
219
|
+
self, f: Callable[[L], Xor[U, R]], fallback_right: R
|
|
220
|
+
) -> Xor[U, R]:
|
|
221
|
+
"""Flatmap `Xor` with function `f` with fallback right
|
|
222
|
+
|
|
223
|
+
:: warning:
|
|
224
|
+
Swallows exceptions.
|
|
225
|
+
|
|
226
|
+
:param fallback_right: fallback value if exception thrown
|
|
227
|
+
|
|
228
|
+
"""
|
|
229
|
+
if self._side == RIGHT:
|
|
230
|
+
return cast(Xor[U, R], self)
|
|
231
|
+
|
|
232
|
+
applied: MayBe[Xor[U, R]] = MayBe()
|
|
233
|
+
fall_back: MayBe[Xor[U, R]] = MayBe()
|
|
234
|
+
try:
|
|
235
|
+
if self:
|
|
236
|
+
applied = MayBe(f(cast(L, self._value)))
|
|
237
|
+
except (
|
|
238
|
+
LookupError,
|
|
239
|
+
ValueError,
|
|
240
|
+
TypeError,
|
|
241
|
+
BufferError,
|
|
242
|
+
ArithmeticError,
|
|
243
|
+
RecursionError,
|
|
244
|
+
ReferenceError,
|
|
245
|
+
RuntimeError,
|
|
246
|
+
):
|
|
247
|
+
fall_back = MayBe(cast(Xor[U, R], Xor(fallback_right, RIGHT)))
|
|
248
|
+
|
|
249
|
+
if fall_back:
|
|
250
|
+
return fall_back.get()
|
|
251
|
+
return applied.get()
|
|
252
|
+
|
|
253
|
+
@staticmethod
|
|
254
|
+
def sequence[U, V](sequence_xor_uv: Sequence[Xor[U, V]]) -> Xor[Sequence[U], V]:
|
|
255
|
+
"""Sequence an indexable of type `Xor[~U, ~V]`
|
|
256
|
+
|
|
257
|
+
If the iterated `Xor` values are all lefts, then return an `Xor` of
|
|
258
|
+
an iterable of the left values. Otherwise return a right Xor containing
|
|
259
|
+
the first right encountered.
|
|
260
|
+
|
|
261
|
+
"""
|
|
262
|
+
list_items: list[U] = []
|
|
263
|
+
|
|
264
|
+
for xor_uv in sequence_xor_uv:
|
|
265
|
+
if xor_uv:
|
|
266
|
+
list_items.append(xor_uv.get())
|
|
267
|
+
else:
|
|
268
|
+
return Xor(xor_uv.get_right().get(), RIGHT)
|
|
269
|
+
|
|
270
|
+
sequence_type = cast(Sequence[U], type(sequence_xor_uv))
|
|
271
|
+
|
|
272
|
+
return Xor(sequence_type(list_items)) # type: ignore # subclass will be callable
|
|
273
|
+
|
|
274
|
+
@staticmethod
|
|
275
|
+
def failable_call[T, V](f: Callable[[T], V], left: T) -> Xor[V, Exception]:
|
|
276
|
+
"""Return Xor wrapped result of a function call that can fail
|
|
277
|
+
|
|
278
|
+
:: warning:
|
|
279
|
+
Swallows exceptions.
|
|
280
|
+
"""
|
|
281
|
+
try:
|
|
282
|
+
xor_return = Xor[V, Exception](f(left), LEFT)
|
|
283
|
+
except (
|
|
284
|
+
LookupError,
|
|
285
|
+
ValueError,
|
|
286
|
+
TypeError,
|
|
287
|
+
BufferError,
|
|
288
|
+
ArithmeticError,
|
|
289
|
+
RecursionError,
|
|
290
|
+
ReferenceError,
|
|
291
|
+
RuntimeError,
|
|
292
|
+
) as exc:
|
|
293
|
+
xor_return = Xor(exc, RIGHT)
|
|
294
|
+
|
|
295
|
+
return xor_return
|
|
296
|
+
|
|
297
|
+
@staticmethod
|
|
298
|
+
def failable_index[V](v: Sequence[V], ii: int) -> Xor[V, Exception]:
|
|
299
|
+
"""Return an Xor of an indexed value that can fail.
|
|
300
|
+
|
|
301
|
+
:: warning:
|
|
302
|
+
Swallows exceptions.
|
|
303
|
+
"""
|
|
304
|
+
try:
|
|
305
|
+
xor_return = Xor[V, Exception](v[ii], LEFT)
|
|
306
|
+
except (
|
|
307
|
+
IndexError,
|
|
308
|
+
TypeError,
|
|
309
|
+
ArithmeticError,
|
|
310
|
+
RuntimeError,
|
|
311
|
+
) as exc:
|
|
312
|
+
xor_return = Xor(exc, RIGHT)
|
|
313
|
+
|
|
314
|
+
return xor_return
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pythonic-fp.containers
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Developer Tools - Container-like data structures
|
|
5
|
+
Keywords: boxes,functional_tuple,immutable_list,maybe
|
|
6
|
+
Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Framework :: Pytest
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: pythonic-fp.fptools >=3.0.0
|
|
18
|
+
Requires-Dist: pythonic-fp.iterables >=3.0.0
|
|
19
|
+
Project-URL: Changelog, https://grscheller.github.io/pythonic-fp-containers/html/changelog.html
|
|
20
|
+
Project-URL: Documentation, https://grscheller.github.io/pythonic-fp-containers/html/
|
|
21
|
+
Project-URL: Homepage, https://github.com/grscheller/pythonic-fp/blob/main/README.md
|
|
22
|
+
Project-URL: Source, https://github.com/grscheller/pythonic-fp-containers
|
|
23
|
+
|
|
24
|
+
# Pythonic FP - Containers
|
|
25
|
+
|
|
26
|
+
PyPI project
|
|
27
|
+
[pythonic-fp.containers](https://pypi.org/project/pythonic-fp.containers/)
|
|
28
|
+
implements a Python package of container like data structures.
|
|
29
|
+
|
|
30
|
+
| Description | Module |
|
|
31
|
+
|:----------- |:----------- |
|
|
32
|
+
| Single item box | *module* `pythonic_fp.containers.box` |
|
|
33
|
+
| Functional tuple | *module* `pythonic_fp.containers.functional_tuple` |
|
|
34
|
+
| Immutable list | *module* `pythonic_fp.containers.immutable_list` |
|
|
35
|
+
| Maybe monad | *module* `pythonic_fp.containers.maybe` |
|
|
36
|
+
| Either monad | *module* `pythonic_fp.containers.xor` |
|
|
37
|
+
|
|
38
|
+
This PyPI project is part of of the grscheller
|
|
39
|
+
[pythonic-fp namespace projects](https://github.com/grscheller/pythonic-fp/blob/main/README.md).
|
|
40
|
+
|
|
41
|
+
## Documentation
|
|
42
|
+
|
|
43
|
+
Documentation hosted on
|
|
44
|
+
[GitHub Pages](https://grscheller.github.io/pythonic-fp-containers/html).
|
|
45
|
+
|
|
46
|
+
## Copyright and License
|
|
47
|
+
|
|
48
|
+
Copyright (c) 2023-2025 Geoffrey R. Scheller. Licensed under the Apache
|
|
49
|
+
License, Version 2.0. See the LICENSE file for details.
|
|
50
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
pythonic_fp/containers/__init__.py,sha256=LAacANoS0XJptegwYwchEOrYEaOFznEtUG54TGG3G4Y,780
|
|
2
|
+
pythonic_fp/containers/box.py,sha256=mlj1Ua1UKtdtKSChUV43q1_S5kHi0GVcFxBz593YwmE,4723
|
|
3
|
+
pythonic_fp/containers/functional_tuple.py,sha256=hcQXzU9M4OGnvcnbWvB8JpyJBKDwrXOyo9Sr8IGZDxY,6329
|
|
4
|
+
pythonic_fp/containers/immutable_list.py,sha256=cfmevJhofCuUh1FhGIlQMWimgNN-eGbcRgweXjEEWsE,7360
|
|
5
|
+
pythonic_fp/containers/maybe.py,sha256=W2J099F2Cn8PxbEPfiYirQPdD1VynrFuIE7sm9UGOBI,6719
|
|
6
|
+
pythonic_fp/containers/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
7
|
+
pythonic_fp/containers/xor.py,sha256=eKfm5OjFl5EXZRZqErEpf7hIEGQkRsF5r3FpXoCKgJQ,9446
|
|
8
|
+
pythonic_fp_containers-2.0.0.dist-info/licenses/LICENSE,sha256=IJMkVWQUVy-SonK6SMaMotDQ6Zb-lVi2bUTiGp1GUO8,10774
|
|
9
|
+
pythonic_fp_containers-2.0.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
10
|
+
pythonic_fp_containers-2.0.0.dist-info/METADATA,sha256=yJqZs_TVSre4fnO8erOHgaTX6TSHJSuBedT6fy8q9Po,2048
|
|
11
|
+
pythonic_fp_containers-2.0.0.dist-info/RECORD,,
|