pythonic-fp-fptools 5.0.0__tar.gz → 5.1.0__tar.gz
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_fptools-5.0.0 → pythonic_fp_fptools-5.1.0}/.gitignore +1 -0
- {pythonic_fp_fptools-5.0.0 → pythonic_fp_fptools-5.1.0}/CHANGELOG.rst +8 -1
- {pythonic_fp_fptools-5.0.0 → pythonic_fp_fptools-5.1.0}/PKG-INFO +13 -12
- {pythonic_fp_fptools-5.0.0 → pythonic_fp_fptools-5.1.0}/README.rst +7 -7
- {pythonic_fp_fptools-5.0.0 → pythonic_fp_fptools-5.1.0}/pyproject.toml +24 -17
- {pythonic_fp_fptools-5.0.0 → pythonic_fp_fptools-5.1.0}/src/pythonic_fp/fptools/__init__.py +3 -1
- {pythonic_fp_fptools-5.0.0 → pythonic_fp_fptools-5.1.0}/src/pythonic_fp/fptools/either.py +93 -61
- pythonic_fp_fptools-5.1.0/src/pythonic_fp/fptools/either.pyi +36 -0
- {pythonic_fp_fptools-5.0.0 → pythonic_fp_fptools-5.1.0}/src/pythonic_fp/fptools/function.py +34 -12
- pythonic_fp_fptools-5.1.0/src/pythonic_fp/fptools/function.pyi +11 -0
- {pythonic_fp_fptools-5.0.0 → pythonic_fp_fptools-5.1.0}/src/pythonic_fp/fptools/lazy.py +58 -48
- pythonic_fp_fptools-5.1.0/src/pythonic_fp/fptools/lazy.pyi +18 -0
- pythonic_fp_fptools-5.1.0/src/pythonic_fp/fptools/maybe.py +138 -0
- pythonic_fp_fptools-5.1.0/src/pythonic_fp/fptools/maybe.pyi +25 -0
- {pythonic_fp_fptools-5.0.0 → pythonic_fp_fptools-5.1.0}/src/pythonic_fp/fptools/state.py +17 -52
- pythonic_fp_fptools-5.1.0/src/pythonic_fp/fptools/state.pyi +23 -0
- {pythonic_fp_fptools-5.0.0 → pythonic_fp_fptools-5.1.0}/tests/either/test_either.py +0 -2
- {pythonic_fp_fptools-5.0.0 → pythonic_fp_fptools-5.1.0}/tests/either/test_sequence_either.py +1 -3
- pythonic_fp_fptools-5.1.0/tests/either/test_str_repr_either.py +74 -0
- {pythonic_fp_fptools-5.0.0 → pythonic_fp_fptools-5.1.0}/tests/function/test_function.py +9 -7
- {pythonic_fp_fptools-5.0.0 → pythonic_fp_fptools-5.1.0}/tests/lazy/test_lazy.py +0 -2
- {pythonic_fp_fptools-5.0.0 → pythonic_fp_fptools-5.1.0}/tests/maybe/maybe.py +1 -2
- {pythonic_fp_fptools-5.0.0 → pythonic_fp_fptools-5.1.0}/tests/maybe/test_sequence_maybe.py +4 -6
- pythonic_fp_fptools-5.0.0/tests/either/test_str_repr_either.py → pythonic_fp_fptools-5.1.0/tests/maybe/test_str_repr_maybe.py +0 -60
- pythonic_fp_fptools-5.0.0/src/pythonic_fp/fptools/maybe.py +0 -141
- pythonic_fp_fptools-5.0.0/tests/bool/test_bool.py +0 -195
- pythonic_fp_fptools-5.0.0/tests/maybe/test_str_repr_maybe.py +0 -194
- {pythonic_fp_fptools-5.0.0 → pythonic_fp_fptools-5.1.0}/LICENSE +0 -0
- {pythonic_fp_fptools-5.0.0 → pythonic_fp_fptools-5.1.0}/src/pythonic_fp/fptools/py.typed +0 -0
- {pythonic_fp_fptools-5.0.0 → pythonic_fp_fptools-5.1.0}/tests/state/test_state.py +0 -0
|
@@ -17,7 +17,14 @@ See `Semantic Versioning 2.0.0 <https://semver.org>`_.
|
|
|
17
17
|
Releases and Important Milestones
|
|
18
18
|
---------------------------------
|
|
19
19
|
|
|
20
|
-
5.
|
|
20
|
+
5.1.0 - TBD
|
|
21
|
+
~~~~~~~~~~~
|
|
22
|
+
|
|
23
|
+
Updated docstrings for new Sphinx docs structure. Probably just a PATCH release,
|
|
24
|
+
made it a MINOR release due to introducing .pyi files.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
5.0.0 - 2025-08-02
|
|
21
28
|
~~~~~~~~~~~~~~~~~~
|
|
22
29
|
|
|
23
30
|
Coordinated entire project pythonic-fp PyPI deployment.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pythonic-fp-fptools
|
|
3
|
-
Version: 5.
|
|
3
|
+
Version: 5.1.0
|
|
4
4
|
Summary: Pythonic FP - Functional Programming Tools
|
|
5
5
|
Keywords: either,fp,functional,functional programming,lazy,maybe,monad,non-strict
|
|
6
6
|
Author-email: "Geoffrey R. Scheller" <geoffrey@scheller.com>
|
|
7
7
|
Requires-Python: >=3.12
|
|
8
8
|
Description-Content-Type: text/x-rst
|
|
9
|
-
Classifier: Development Status ::
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
10
|
Classifier: Framework :: Pytest
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
@@ -14,10 +14,11 @@ Classifier: Operating System :: OS Independent
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.13
|
|
15
15
|
Classifier: Typing :: Typed
|
|
16
16
|
License-File: LICENSE
|
|
17
|
-
Requires-Dist: pythonic-fp-
|
|
18
|
-
Requires-Dist: pythonic-fp-
|
|
17
|
+
Requires-Dist: pythonic-fp-booleans>=1.1.2
|
|
18
|
+
Requires-Dist: pythonic-fp-circulararray>=5.3.3
|
|
19
|
+
Requires-Dist: pythonic-fp-sentinels>=2.1.1
|
|
19
20
|
Requires-Dist: pytest>=8.4.1 ; extra == "test"
|
|
20
|
-
Requires-Dist: pythonic-fp-containers>=3.0.
|
|
21
|
+
Requires-Dist: pythonic-fp-containers>=3.0.1 ; extra == "test"
|
|
21
22
|
Project-URL: Changelog, https://github.com/grscheller/pythonic-fp-fptools/blob/main/CHANGELOG.rst
|
|
22
23
|
Project-URL: Documentation, https://grscheller.github.io/pythonic-fp/fptools/development/build/html/releases.html
|
|
23
24
|
Project-URL: Homepage, https://github.com/grscheller/pythonic-fp/blob/main/README.md
|
|
@@ -31,13 +32,16 @@ PyPI project
|
|
|
31
32
|
`pythonic-fp.fptools
|
|
32
33
|
<https://pypi.org/project/pythonic-fp.fptools>`_.
|
|
33
34
|
|
|
34
|
-
Tools to aid with functional programming in Python
|
|
35
|
-
|
|
35
|
+
Tools to aid with functional programming in Python while still
|
|
36
|
+
endeavoring to be Pythonic.
|
|
36
37
|
|
|
37
38
|
- Functions as first class objects
|
|
38
39
|
- Lazy (non-strict) function evaluation
|
|
39
40
|
- Maybe monad - representing a possible missing value
|
|
40
|
-
- Either monad -
|
|
41
|
+
- Either monad - representing either a LEFT or RIGHT value, not both
|
|
42
|
+
|
|
43
|
+
- left biased
|
|
44
|
+
|
|
41
45
|
- State monad implementation
|
|
42
46
|
|
|
43
47
|
- pure FP handling of state (the state monad)
|
|
@@ -45,13 +49,10 @@ remain Pythonic.
|
|
|
45
49
|
|
|
46
50
|
- the monad encapsulates a state transformation, not a "state"
|
|
47
51
|
|
|
48
|
-
This PyPI project is part of
|
|
52
|
+
This PyPI project is part of the
|
|
49
53
|
`pythonic-fp namespace projects
|
|
50
54
|
<https://github.com/grscheller/pythonic-fp/blob/main/README.md>`_
|
|
51
55
|
|
|
52
|
-
**Warning:** The maintainer intends to break out the first, forth and
|
|
53
|
-
fifth modules to their own repos sometime in the near future.
|
|
54
|
-
|
|
55
56
|
Documentation
|
|
56
57
|
-------------
|
|
57
58
|
|
|
@@ -5,13 +5,16 @@ PyPI project
|
|
|
5
5
|
`pythonic-fp.fptools
|
|
6
6
|
<https://pypi.org/project/pythonic-fp.fptools>`_.
|
|
7
7
|
|
|
8
|
-
Tools to aid with functional programming in Python
|
|
9
|
-
|
|
8
|
+
Tools to aid with functional programming in Python while still
|
|
9
|
+
endeavoring to be Pythonic.
|
|
10
10
|
|
|
11
11
|
- Functions as first class objects
|
|
12
12
|
- Lazy (non-strict) function evaluation
|
|
13
13
|
- Maybe monad - representing a possible missing value
|
|
14
|
-
- Either monad -
|
|
14
|
+
- Either monad - representing either a LEFT or RIGHT value, not both
|
|
15
|
+
|
|
16
|
+
- left biased
|
|
17
|
+
|
|
15
18
|
- State monad implementation
|
|
16
19
|
|
|
17
20
|
- pure FP handling of state (the state monad)
|
|
@@ -19,13 +22,10 @@ remain Pythonic.
|
|
|
19
22
|
|
|
20
23
|
- the monad encapsulates a state transformation, not a "state"
|
|
21
24
|
|
|
22
|
-
This PyPI project is part of
|
|
25
|
+
This PyPI project is part of the
|
|
23
26
|
`pythonic-fp namespace projects
|
|
24
27
|
<https://github.com/grscheller/pythonic-fp/blob/main/README.md>`_
|
|
25
28
|
|
|
26
|
-
**Warning:** The maintainer intends to break out the first, forth and
|
|
27
|
-
fifth modules to their own repos sometime in the near future.
|
|
28
|
-
|
|
29
29
|
Documentation
|
|
30
30
|
-------------
|
|
31
31
|
|
|
@@ -1,10 +1,6 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["flit_core>=3.12,<4"]
|
|
3
|
-
build-backend = "flit_core.buildapi"
|
|
4
|
-
|
|
5
1
|
[project]
|
|
6
2
|
name = "pythonic-fp-fptools"
|
|
7
|
-
version = "5.
|
|
3
|
+
version = "5.1.0"
|
|
8
4
|
readme = "README.rst"
|
|
9
5
|
requires-python = ">=3.12"
|
|
10
6
|
license = { file = "LICENSE" }
|
|
@@ -20,7 +16,7 @@ keywords = [
|
|
|
20
16
|
"non-strict",
|
|
21
17
|
]
|
|
22
18
|
classifiers = [
|
|
23
|
-
"Development Status ::
|
|
19
|
+
"Development Status :: 3 - Alpha",
|
|
24
20
|
"Framework :: Pytest",
|
|
25
21
|
"Intended Audience :: Developers",
|
|
26
22
|
"License :: OSI Approved :: Apache Software License",
|
|
@@ -29,30 +25,37 @@ classifiers = [
|
|
|
29
25
|
"Typing :: Typed",
|
|
30
26
|
]
|
|
31
27
|
dependencies = [
|
|
32
|
-
"pythonic-fp-
|
|
33
|
-
"pythonic-fp-
|
|
28
|
+
"pythonic-fp-booleans>=1.1.2",
|
|
29
|
+
"pythonic-fp-circulararray>=5.3.3",
|
|
30
|
+
"pythonic-fp-sentinels>=2.1.1",
|
|
34
31
|
]
|
|
35
32
|
dynamic = ["description"]
|
|
36
33
|
|
|
37
|
-
[project.optional-dependencies]
|
|
38
|
-
test = [
|
|
39
|
-
"pytest>=8.4.1",
|
|
40
|
-
"pythonic-fp-containers>=3.0.0",
|
|
41
|
-
]
|
|
42
|
-
|
|
43
34
|
[project.urls]
|
|
44
35
|
Changelog = "https://github.com/grscheller/pythonic-fp-fptools/blob/main/CHANGELOG.rst"
|
|
45
36
|
Documentation = "https://grscheller.github.io/pythonic-fp/fptools/development/build/html/releases.html"
|
|
46
37
|
Homepage = "https://github.com/grscheller/pythonic-fp/blob/main/README.md"
|
|
47
38
|
Source = "https://github.com/grscheller/pythonic-fp-fptools"
|
|
48
39
|
|
|
40
|
+
[project.optional-dependencies]
|
|
41
|
+
test = [
|
|
42
|
+
"pytest>=8.4.1",
|
|
43
|
+
"pythonic-fp-containers>=3.0.1",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
[build-system]
|
|
47
|
+
requires = ["flit_core>=3.12,<4"]
|
|
48
|
+
build-backend = "flit_core.buildapi"
|
|
49
|
+
|
|
49
50
|
[tool.flit.module]
|
|
50
51
|
name = "pythonic_fp.fptools"
|
|
51
52
|
|
|
52
53
|
[tool.mypy]
|
|
53
|
-
enable_incomplete_feature =
|
|
54
|
+
enable_incomplete_feature = "NewGenericSyntax"
|
|
55
|
+
explicit_package_bases = true
|
|
54
56
|
implicit_reexport = false
|
|
55
57
|
local_partial_types = true
|
|
58
|
+
namespace_packages = true
|
|
56
59
|
warn_redundant_casts = true
|
|
57
60
|
warn_return_any = true
|
|
58
61
|
warn_unused_configs = true
|
|
@@ -60,21 +63,25 @@ warn_unused_configs = true
|
|
|
60
63
|
[tool.pylsp-mypy]
|
|
61
64
|
enabled = true
|
|
62
65
|
live-mode = true
|
|
66
|
+
dmypy = false
|
|
63
67
|
strict = true
|
|
64
68
|
report_progress = true
|
|
65
69
|
|
|
66
70
|
[tool.pytest.ini_options]
|
|
71
|
+
addopts = "-ra"
|
|
67
72
|
consider_namespace_packages = true
|
|
68
73
|
testpaths = ["tests/"]
|
|
69
|
-
addopts = "-ra"
|
|
70
74
|
|
|
71
75
|
[tool.ruff]
|
|
72
76
|
target-version = "py313"
|
|
73
|
-
ignore = ["E741"]
|
|
74
77
|
|
|
75
78
|
[tool.ruff.lint.flake8-quotes]
|
|
76
79
|
docstring-quotes = "double"
|
|
77
80
|
|
|
81
|
+
[tool.ruff.lint.per-file-ignores]
|
|
82
|
+
"tests/*" = ["C901"]
|
|
83
|
+
"**/*.py" = ["E741"]
|
|
84
|
+
|
|
78
85
|
[tool.ruff.format]
|
|
79
86
|
quote-style = "single"
|
|
80
87
|
docstring-code-line-length = 72
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
"""
|
|
15
|
+
"""
|
|
16
|
+
Pythonic FP - Functional Programming Tools
|
|
17
|
+
==========================================
|
|
16
18
|
|
|
17
19
|
Functions as first class objects, Lazy (non-strict) function evaluation,
|
|
18
20
|
and classical Functional Programming data structures.
|
|
@@ -12,46 +12,52 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
from __future__ import annotations
|
|
16
|
-
|
|
17
15
|
__all__ = ['Either', 'LEFT', 'RIGHT']
|
|
18
16
|
|
|
19
17
|
from collections.abc import Callable, Iterator, Sequence
|
|
20
|
-
from typing import cast,
|
|
21
|
-
from pythonic_fp.
|
|
22
|
-
from pythonic_fp.singletons.sentinel import Sentinel as _Sentinel
|
|
18
|
+
from typing import cast, overload
|
|
19
|
+
from pythonic_fp.booleans.subtypable import SBool
|
|
23
20
|
from .maybe import MayBe
|
|
24
21
|
|
|
25
|
-
L = TypeVar('L', covariant=True)
|
|
26
|
-
R = TypeVar('R', covariant=True)
|
|
27
22
|
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
class EitherBool(SBool):
|
|
24
|
+
def __repr__(self) -> str:
|
|
25
|
+
if self:
|
|
26
|
+
return 'LEFT'
|
|
27
|
+
return 'RIGHT'
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
LEFT = EitherBool(True)
|
|
31
|
+
RIGHT = EitherBool(False)
|
|
30
32
|
|
|
31
33
|
|
|
32
34
|
class Either[L, R]:
|
|
33
|
-
"""
|
|
35
|
+
"""
|
|
36
|
+
Either Monad
|
|
37
|
+
------------
|
|
38
|
+
|
|
39
|
+
Data structure semantically containing either a left
|
|
34
40
|
or a right value, but not both.
|
|
35
41
|
|
|
36
42
|
Implements a left biased Either Monad.
|
|
37
43
|
|
|
38
|
-
-
|
|
39
|
-
-
|
|
44
|
+
- ``Either(value: +L, LEFT)`` produces a left ``Either``
|
|
45
|
+
- ``Either(value: +L, RIGHT)`` produces a right ``Either``
|
|
40
46
|
|
|
41
47
|
In a Boolean context
|
|
42
48
|
|
|
43
|
-
-
|
|
44
|
-
-
|
|
49
|
+
- A left ``Either`` is "truthy"
|
|
50
|
+
- A right ``Either`` is "falsy"
|
|
45
51
|
|
|
46
|
-
Two
|
|
52
|
+
Two ``Either`` objects compare as equal when
|
|
47
53
|
|
|
48
54
|
- both are left values or both are right values whose values
|
|
49
55
|
|
|
50
56
|
- are the same object
|
|
51
57
|
- compare as equal
|
|
52
58
|
|
|
53
|
-
Immutable, an
|
|
54
|
-
map & bind return new instances
|
|
59
|
+
Immutable, an ``Either`` does not change after being created.
|
|
60
|
+
Therefore ``map`` & ``bind`` return new instances.
|
|
55
61
|
|
|
56
62
|
.. warning::
|
|
57
63
|
|
|
@@ -60,31 +66,36 @@ class Either[L, R]:
|
|
|
60
66
|
|
|
61
67
|
.. note::
|
|
62
68
|
|
|
63
|
-
``Either(value: +L, side: Left): Either[
|
|
64
|
-
``Either(value: +R, side: Right): Either[
|
|
69
|
+
``Either(value: +L, side: Left): Either[L, R] -> left: Either[L, R]``
|
|
70
|
+
``Either(value: +R, side: Right): Either[L, R] -> right: Either[L, R]``
|
|
65
71
|
|
|
66
72
|
"""
|
|
73
|
+
|
|
67
74
|
__slots__ = '_value', '_side'
|
|
68
75
|
__match_args__ = ('_value', '_side')
|
|
69
76
|
|
|
70
|
-
U = TypeVar('U', covariant=True)
|
|
71
|
-
V = TypeVar('V', covariant=True)
|
|
72
|
-
T = TypeVar('T')
|
|
73
|
-
|
|
74
77
|
@overload
|
|
75
|
-
def __init__(self, value: L
|
|
78
|
+
def __init__(self, value: L) -> None: ...
|
|
76
79
|
@overload
|
|
77
|
-
def __init__(self, value:
|
|
78
|
-
|
|
79
|
-
def __init__(self, value:
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
def __init__(self, value: L, side: EitherBool) -> None: ...
|
|
81
|
+
@overload
|
|
82
|
+
def __init__(self, value: R, side: EitherBool) -> None: ...
|
|
83
|
+
|
|
84
|
+
def __init__(self, value: L | R, side: EitherBool = LEFT) -> None:
|
|
85
|
+
self._value: L | R
|
|
86
|
+
self._side: EitherBool
|
|
87
|
+
if side:
|
|
88
|
+
self._value = value
|
|
89
|
+
self._side = LEFT
|
|
90
|
+
else:
|
|
91
|
+
self._value = value
|
|
92
|
+
self._side = RIGHT
|
|
82
93
|
|
|
83
94
|
def __hash__(self) -> int:
|
|
84
|
-
return hash((
|
|
95
|
+
return hash((self, self._value, self._side))
|
|
85
96
|
|
|
86
97
|
def __bool__(self) -> bool:
|
|
87
|
-
return self._side
|
|
98
|
+
return self._side is LEFT
|
|
88
99
|
|
|
89
100
|
def __iter__(self) -> Iterator[L]:
|
|
90
101
|
if self:
|
|
@@ -118,7 +129,7 @@ class Either[L, R]:
|
|
|
118
129
|
|
|
119
130
|
return False
|
|
120
131
|
|
|
121
|
-
def get(self) -> L
|
|
132
|
+
def get(self) -> L:
|
|
122
133
|
"""Get value if a left.
|
|
123
134
|
|
|
124
135
|
.. warning::
|
|
@@ -127,8 +138,7 @@ class Either[L, R]:
|
|
|
127
138
|
is a right. Best practice is to first check the ``Either`` in
|
|
128
139
|
a boolean context.
|
|
129
140
|
|
|
130
|
-
:
|
|
131
|
-
:rtype: +L
|
|
141
|
+
:returns: its value if a Left
|
|
132
142
|
:raises ValueError: if not a left
|
|
133
143
|
|
|
134
144
|
"""
|
|
@@ -138,10 +148,9 @@ class Either[L, R]:
|
|
|
138
148
|
return cast(L, self._value)
|
|
139
149
|
|
|
140
150
|
def get_left(self) -> MayBe[L]:
|
|
141
|
-
"""Get value of
|
|
151
|
+
"""Get value of ``Either`` if a left. Safer version of ``get`` method.
|
|
142
152
|
|
|
143
|
-
|
|
144
|
-
- if `Either` contains a right value, return MayBe()
|
|
153
|
+
:returns: MayBe[L]
|
|
145
154
|
|
|
146
155
|
"""
|
|
147
156
|
if self._side == LEFT:
|
|
@@ -149,47 +158,65 @@ class Either[L, R]:
|
|
|
149
158
|
return MayBe()
|
|
150
159
|
|
|
151
160
|
def get_right(self) -> MayBe[R]:
|
|
152
|
-
"""Get value of
|
|
161
|
+
"""Get value of ``Either`` if a right.
|
|
153
162
|
|
|
154
|
-
|
|
155
|
-
- if `Either` contains a left value, return MayBe()
|
|
163
|
+
:returns: MayBe[R]
|
|
156
164
|
|
|
157
165
|
"""
|
|
158
166
|
if self._side == RIGHT:
|
|
159
167
|
return MayBe(cast(R, self._value))
|
|
160
168
|
return MayBe()
|
|
161
169
|
|
|
162
|
-
def map_right[V](self, f: Callable[[R], V]) -> Either[L, V]:
|
|
163
|
-
"""Construct new Either with a different right.
|
|
170
|
+
def map_right[V](self, f: Callable[[R], V]) -> 'Either[L, V]':
|
|
171
|
+
"""Construct new Either with a different right.
|
|
172
|
+
|
|
173
|
+
:param f: function to map a right value
|
|
174
|
+
:returns: a new Either if a right, otherwise itself
|
|
175
|
+
|
|
176
|
+
"""
|
|
164
177
|
if self._side == LEFT:
|
|
165
178
|
return cast(Either[L, V], self)
|
|
166
179
|
return Either[L, V](f(cast(R, self._value)), RIGHT)
|
|
167
180
|
|
|
168
|
-
def map[U](self, f: Callable[[L], U]) -> Either[U, R]:
|
|
169
|
-
"""Map over if a left value. Return new instance.
|
|
181
|
+
def map[U](self, f: Callable[[L], U]) -> 'Either[U, R]':
|
|
182
|
+
"""Map over if a left value. Return new instance.
|
|
183
|
+
|
|
184
|
+
:param f: function to map a left value
|
|
185
|
+
:returns: a new Either if a left, otherwise itself
|
|
186
|
+
|
|
187
|
+
"""
|
|
170
188
|
if self._side == RIGHT:
|
|
171
189
|
return cast(Either[U, R], self)
|
|
172
190
|
return Either(f(cast(L, self._value)), LEFT)
|
|
173
191
|
|
|
174
|
-
def bind[U](self, f: Callable[[L], Either[U, R]]) -> Either[U, R]:
|
|
175
|
-
"""Flatmap over the left value, propagate right values.
|
|
192
|
+
def bind[U](self, f: 'Callable[[L], Either[U, R]]') -> 'Either[U, R]':
|
|
193
|
+
"""Flatmap over the left value, propagate right values.
|
|
194
|
+
|
|
195
|
+
:param f: function to flatmap a left value
|
|
196
|
+
:returns: a new Either if a left, otherwise itself
|
|
197
|
+
|
|
198
|
+
"""
|
|
176
199
|
if self:
|
|
177
200
|
return f(cast(L, self._value))
|
|
178
201
|
return cast(Either[U, R], self)
|
|
179
202
|
|
|
180
|
-
def map_except[U](self, f: Callable[[L], U], fallback_right: R) -> Either[U, R]:
|
|
203
|
+
def map_except[U](self, f: Callable[[L], U], fallback_right: R) -> 'Either[U, R]':
|
|
181
204
|
"""Map over if a left value - with fallback upon exception.
|
|
182
205
|
|
|
183
|
-
- if
|
|
206
|
+
- if ``Either`` is a left then map ``f`` over its value
|
|
207
|
+
|
|
208
|
+
- if ``f`` returns normally, then return a left ``Either[U, R]``
|
|
209
|
+
- if ``f`` raises an exception, return right ``Either[U, R]``
|
|
184
210
|
|
|
185
|
-
|
|
186
|
-
- if `f` unsuccessful return right `Either[+U, +R]`
|
|
211
|
+
- if ``Either`` is a right
|
|
187
212
|
|
|
188
|
-
|
|
213
|
+
- return new ``Either(right=self._right): Either[+U, +R]``
|
|
189
214
|
|
|
190
|
-
|
|
215
|
+
.. warning::
|
|
216
|
+
Swallows exceptions.
|
|
191
217
|
|
|
192
|
-
|
|
218
|
+
.. note
|
|
219
|
+
The fallback type must be the same type as the ``Either``
|
|
193
220
|
|
|
194
221
|
"""
|
|
195
222
|
if self._side == RIGHT:
|
|
@@ -216,14 +243,19 @@ class Either[L, R]:
|
|
|
216
243
|
return applied.get()
|
|
217
244
|
|
|
218
245
|
def bind_except[U](
|
|
219
|
-
self, f: Callable[[L], Either[U, R]], fallback_right: R
|
|
220
|
-
) -> Either[U, R]:
|
|
221
|
-
"""Flatmap
|
|
246
|
+
self, f: 'Callable[[L], Either[U, R]]', fallback_right: R
|
|
247
|
+
) -> 'Either[U, R]':
|
|
248
|
+
"""Flatmap ``Either`` with function ``f`` with a fallback right
|
|
249
|
+
if exception is thrown.
|
|
222
250
|
|
|
223
251
|
.. warning::
|
|
224
252
|
Swallows exceptions.
|
|
225
253
|
|
|
254
|
+
.. note
|
|
255
|
+
The fallback type must be the same type as the ``Either``.
|
|
256
|
+
|
|
226
257
|
:param fallback_right: fallback value if exception thrown
|
|
258
|
+
:returns: a successfully mapped left, a propagated right, or a right with fallback value
|
|
227
259
|
|
|
228
260
|
"""
|
|
229
261
|
if self._side == RIGHT:
|
|
@@ -252,12 +284,12 @@ class Either[L, R]:
|
|
|
252
284
|
|
|
253
285
|
@staticmethod
|
|
254
286
|
def sequence[U, V](
|
|
255
|
-
sequence_xor_uv: Sequence[Either[U, V]],
|
|
256
|
-
) -> Either[Sequence[U], V]:
|
|
257
|
-
"""Sequence
|
|
287
|
+
sequence_xor_uv: 'Sequence[Either[U, V]]',
|
|
288
|
+
) -> 'Either[Sequence[U], V]':
|
|
289
|
+
"""Sequence a sequence subtype of Sequence[Either[U, V]]``
|
|
258
290
|
|
|
259
|
-
If the iterated
|
|
260
|
-
an iterable of the left values. Otherwise return a right Either containing
|
|
291
|
+
If the iterated ``Either`` values are all lefts, then return an ``Either`` of
|
|
292
|
+
an iterable of the left values. Otherwise return a right ``Either`` containing
|
|
261
293
|
the first right encountered.
|
|
262
294
|
|
|
263
295
|
"""
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from .maybe import MayBe
|
|
2
|
+
from _typeshed import Incomplete
|
|
3
|
+
from collections.abc import Callable, Iterator, Sequence
|
|
4
|
+
from pythonic_fp.booleans.subtypable import SBool
|
|
5
|
+
from typing import overload
|
|
6
|
+
|
|
7
|
+
__all__ = ['Either', 'LEFT', 'RIGHT']
|
|
8
|
+
|
|
9
|
+
class EitherBool(SBool): ...
|
|
10
|
+
|
|
11
|
+
LEFT: Incomplete
|
|
12
|
+
RIGHT: Incomplete
|
|
13
|
+
|
|
14
|
+
class Either[L, R]:
|
|
15
|
+
__match_args__: Incomplete
|
|
16
|
+
@overload
|
|
17
|
+
def __init__(self, value: L) -> None: ...
|
|
18
|
+
@overload
|
|
19
|
+
def __init__(self, value: L, side: EitherBool) -> None: ...
|
|
20
|
+
@overload
|
|
21
|
+
def __init__(self, value: R, side: EitherBool) -> None: ...
|
|
22
|
+
def __hash__(self) -> int: ...
|
|
23
|
+
def __bool__(self) -> bool: ...
|
|
24
|
+
def __iter__(self) -> Iterator[L]: ...
|
|
25
|
+
def __len__(self) -> int: ...
|
|
26
|
+
def __eq__(self, other: object) -> bool: ...
|
|
27
|
+
def get(self) -> L: ...
|
|
28
|
+
def get_left(self) -> MayBe[L]: ...
|
|
29
|
+
def get_right(self) -> MayBe[R]: ...
|
|
30
|
+
def map_right[V](self, f: Callable[[R], V]) -> Either[L, V]: ...
|
|
31
|
+
def map[U](self, f: Callable[[L], U]) -> Either[U, R]: ...
|
|
32
|
+
def bind[U](self, f: Callable[[L], Either[U, R]]) -> Either[U, R]: ...
|
|
33
|
+
def map_except[U](self, f: Callable[[L], U], fallback_right: R) -> Either[U, R]: ...
|
|
34
|
+
def bind_except[U](self, f: Callable[[L], Either[U, R]], fallback_right: R) -> Either[U, R]: ...
|
|
35
|
+
@staticmethod
|
|
36
|
+
def sequence[U, V](sequence_xor_uv: Sequence[Either[U, V]]) -> Either[Sequence[U], V]: ...
|
|
@@ -12,11 +12,9 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
modifying function behavior through decorators than functional composition
|
|
19
|
-
and application.
|
|
15
|
+
"""
|
|
16
|
+
FP tools for functions
|
|
17
|
+
======================
|
|
20
18
|
|
|
21
19
|
FP utilities to manipulate and partially apply functions
|
|
22
20
|
|
|
@@ -27,7 +25,6 @@ FP utilities to manipulate and partially apply functions
|
|
|
27
25
|
|
|
28
26
|
"""
|
|
29
27
|
|
|
30
|
-
from __future__ import annotations
|
|
31
28
|
from collections.abc import Callable
|
|
32
29
|
from typing import Any, ParamSpec
|
|
33
30
|
|
|
@@ -37,12 +34,29 @@ P = ParamSpec('P')
|
|
|
37
34
|
|
|
38
35
|
|
|
39
36
|
def swap[U, V, R](f: Callable[[U, V], R]) -> Callable[[V, U], R]:
|
|
40
|
-
"""
|
|
37
|
+
"""
|
|
38
|
+
Swap args
|
|
39
|
+
---------
|
|
40
|
+
|
|
41
|
+
Swap arguments of a two argument function.
|
|
42
|
+
|
|
43
|
+
:param f: Two argument function.
|
|
44
|
+
:returns: A version of ``f`` with its arguments swapped.
|
|
45
|
+
|
|
46
|
+
"""
|
|
41
47
|
return lambda v, u: f(u, v)
|
|
42
48
|
|
|
43
49
|
|
|
44
50
|
def negate[**P](f: Callable[P, bool]) -> Callable[P, bool]:
|
|
45
|
-
"""
|
|
51
|
+
"""
|
|
52
|
+
Negate predicate
|
|
53
|
+
----------------
|
|
54
|
+
|
|
55
|
+
Take a predicate and return its negation.
|
|
56
|
+
|
|
57
|
+
:param f: a function ``f`` which returns a bool
|
|
58
|
+
:returns: the function ``not f``
|
|
59
|
+
"""
|
|
46
60
|
|
|
47
61
|
def ff(*args: P.args, **kwargs: P.kwargs) -> bool:
|
|
48
62
|
return not f(*args, **kwargs)
|
|
@@ -51,7 +65,11 @@ def negate[**P](f: Callable[P, bool]) -> Callable[P, bool]:
|
|
|
51
65
|
|
|
52
66
|
|
|
53
67
|
def sequenced[R](f: Callable[..., R]) -> Callable[[tuple[Any]], R]:
|
|
54
|
-
"""
|
|
68
|
+
"""
|
|
69
|
+
Multi-to-single valued
|
|
70
|
+
----------------------
|
|
71
|
+
|
|
72
|
+
Convert a function with arbitrary positional arguments to one taking
|
|
55
73
|
a tuple of the original arguments.
|
|
56
74
|
|
|
57
75
|
- was awaiting typing and mypy "improvements" to ParamSpec
|
|
@@ -59,11 +77,10 @@ def sequenced[R](f: Callable[..., R]) -> Callable[[tuple[Any]], R]:
|
|
|
59
77
|
- return type: Callable[tuple[P.args], R] ???
|
|
60
78
|
- return type: Callable[[tuple[P.args]], R] ???
|
|
61
79
|
|
|
62
|
-
- not going to happen, `see <https://github.com/python/mypy/pull/18278>`_
|
|
63
|
-
|
|
64
80
|
TODO: Look into replacing this function with a Callable class?
|
|
65
81
|
|
|
66
82
|
"""
|
|
83
|
+
|
|
67
84
|
def ff(tupled_args: tuple[Any]) -> R:
|
|
68
85
|
return f(*tupled_args)
|
|
69
86
|
|
|
@@ -71,12 +88,17 @@ def sequenced[R](f: Callable[..., R]) -> Callable[[tuple[Any]], R]:
|
|
|
71
88
|
|
|
72
89
|
|
|
73
90
|
def partial[**P, R](f: Callable[P, R], *args: Any) -> Callable[..., R]:
|
|
74
|
-
"""
|
|
91
|
+
"""
|
|
92
|
+
Partial application
|
|
93
|
+
-------------------
|
|
94
|
+
|
|
95
|
+
Partially apply arguments to a function, left to right.
|
|
75
96
|
|
|
76
97
|
- type-wise the only thing guaranteed is the return type
|
|
77
98
|
- best practice is to cast the result immediately
|
|
78
99
|
|
|
79
100
|
"""
|
|
101
|
+
|
|
80
102
|
def finish(*rest: Any) -> R:
|
|
81
103
|
return sequenced(f)(args + rest)
|
|
82
104
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from typing import Any, ParamSpec
|
|
3
|
+
|
|
4
|
+
__all__ = ['swap', 'sequenced', 'partial', 'negate']
|
|
5
|
+
|
|
6
|
+
P = ParamSpec('P')
|
|
7
|
+
|
|
8
|
+
def swap[U, V, R](f: Callable[[U, V], R]) -> Callable[[V, U], R]: ...
|
|
9
|
+
def negate[**P](f: Callable[P, bool]) -> Callable[P, bool]: ...
|
|
10
|
+
def sequenced[R](f: Callable[..., R]) -> Callable[[tuple[Any]], R]: ...
|
|
11
|
+
def partial[**P, R](f: Callable[P, R], *args: Any) -> Callable[..., R]: ...
|