functorial 0.1.1__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.
- functorial-0.1.1/.claude/settings.local.json +7 -0
- functorial-0.1.1/.dir-locals.el +4 -0
- functorial-0.1.1/.flake8 +3 -0
- functorial-0.1.1/.gitignore +25 -0
- functorial-0.1.1/.pylintrc +46 -0
- functorial-0.1.1/.python-version +1 -0
- functorial-0.1.1/LICENSE +21 -0
- functorial-0.1.1/PKG-INFO +68 -0
- functorial-0.1.1/README.md +51 -0
- functorial-0.1.1/pyproject.toml +122 -0
- functorial-0.1.1/src/functorial/__about__.py +4 -0
- functorial-0.1.1/src/functorial/__init__.py +29 -0
- functorial-0.1.1/src/functorial/all.py +54 -0
- functorial-0.1.1/src/functorial/alternative.py +36 -0
- functorial-0.1.1/src/functorial/applicative.py +168 -0
- functorial-0.1.1/src/functorial/backwards.py +18 -0
- functorial-0.1.1/src/functorial/bicofunctor.py +48 -0
- functorial-0.1.1/src/functorial/bifunctor.py +52 -0
- functorial-0.1.1/src/functorial/bitraversable.py +41 -0
- functorial-0.1.1/src/functorial/cofunctor.py +70 -0
- functorial-0.1.1/src/functorial/const.py +199 -0
- functorial-0.1.1/src/functorial/dev/__init__.py +1 -0
- functorial-0.1.1/src/functorial/dev/phases.py +93 -0
- functorial-0.1.1/src/functorial/dict.py +76 -0
- functorial-0.1.1/src/functorial/either.py +165 -0
- functorial-0.1.1/src/functorial/examples/__init__.py +1 -0
- functorial-0.1.1/src/functorial/examples/divide_conquer.py +71 -0
- functorial-0.1.1/src/functorial/examples/huffman.py +208 -0
- functorial-0.1.1/src/functorial/foldable.py +227 -0
- functorial-0.1.1/src/functorial/functions.py +419 -0
- functorial-0.1.1/src/functorial/functor.py +89 -0
- functorial-0.1.1/src/functorial/identity.py +55 -0
- functorial-0.1.1/src/functorial/io.py +161 -0
- functorial-0.1.1/src/functorial/lazy.py +47 -0
- functorial-0.1.1/src/functorial/list.py +382 -0
- functorial-0.1.1/src/functorial/maybe.py +204 -0
- functorial-0.1.1/src/functorial/monad.py +126 -0
- functorial-0.1.1/src/functorial/monoids.py +524 -0
- functorial-0.1.1/src/functorial/ntuple.py +131 -0
- functorial-0.1.1/src/functorial/ops.py +44 -0
- functorial-0.1.1/src/functorial/optics/__init__.py +23 -0
- functorial-0.1.1/src/functorial/optics/affine_fold.py +106 -0
- functorial-0.1.1/src/functorial/optics/affine_traversal.py +122 -0
- functorial-0.1.1/src/functorial/optics/all.py +26 -0
- functorial-0.1.1/src/functorial/optics/choice.py +93 -0
- functorial-0.1.1/src/functorial/optics/cochoice.py +54 -0
- functorial-0.1.1/src/functorial/optics/costrong.py +46 -0
- functorial-0.1.1/src/functorial/optics/deprecated/cartesian.py +65 -0
- functorial-0.1.1/src/functorial/optics/deprecated/forget.py +136 -0
- functorial-0.1.1/src/functorial/optics/deprecated/star.py +76 -0
- functorial-0.1.1/src/functorial/optics/fold.py +120 -0
- functorial-0.1.1/src/functorial/optics/generics.py +96 -0
- functorial-0.1.1/src/functorial/optics/getter.py +48 -0
- functorial-0.1.1/src/functorial/optics/iso.py +159 -0
- functorial-0.1.1/src/functorial/optics/ix_fold.py +133 -0
- functorial-0.1.1/src/functorial/optics/ix_lens.py +202 -0
- functorial-0.1.1/src/functorial/optics/ix_traversal.py +128 -0
- functorial-0.1.1/src/functorial/optics/lens.py +178 -0
- functorial-0.1.1/src/functorial/optics/make_lens.py +139 -0
- functorial-0.1.1/src/functorial/optics/optic.py +413 -0
- functorial-0.1.1/src/functorial/optics/prism.py +113 -0
- functorial-0.1.1/src/functorial/optics/profunctors.py +678 -0
- functorial-0.1.1/src/functorial/optics/re_.py +129 -0
- functorial-0.1.1/src/functorial/optics/review.py +156 -0
- functorial-0.1.1/src/functorial/optics/setter.py +64 -0
- functorial-0.1.1/src/functorial/optics/strong.py +75 -0
- functorial-0.1.1/src/functorial/optics/traversal.py +93 -0
- functorial-0.1.1/src/functorial/optics/vl_optics.py +462 -0
- functorial-0.1.1/src/functorial/pair.py +101 -0
- functorial-0.1.1/src/functorial/profunctor.py +61 -0
- functorial-0.1.1/src/functorial/py.typed +0 -0
- functorial-0.1.1/src/functorial/reader.py +94 -0
- functorial-0.1.1/src/functorial/set.py +45 -0
- functorial-0.1.1/src/functorial/singleton.py +214 -0
- functorial-0.1.1/src/functorial/state.py +110 -0
- functorial-0.1.1/src/functorial/traversable.py +112 -0
- functorial-0.1.1/src/functorial/trees.py +820 -0
- functorial-0.1.1/src/functorial/unit.py +10 -0
- functorial-0.1.1/src/functorial/utils.py +137 -0
- functorial-0.1.1/src/functorial/wrappers.py +52 -0
- functorial-0.1.1/src/functorial/writer.py +127 -0
- functorial-0.1.1/tests/__init__.py +1 -0
- functorial-0.1.1/tests/test_examples.py +594 -0
- functorial-0.1.1/tests/test_ixoptics.py +389 -0
- functorial-0.1.1/tests/test_optics.py +491 -0
- functorial-0.1.1/uv.lock +130 -0
functorial-0.1.1/.flake8
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# -*- mode: fundamental; -*-
|
|
2
|
+
|
|
3
|
+
# Python-generated files
|
|
4
|
+
__pycache__/
|
|
5
|
+
*.py[oc]
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
wheels/
|
|
9
|
+
*.egg-info
|
|
10
|
+
.hypothesis
|
|
11
|
+
|
|
12
|
+
# Virtual environments
|
|
13
|
+
.venv
|
|
14
|
+
|
|
15
|
+
# Ignore emacs backup files (at least, the ones I generate)
|
|
16
|
+
.*~
|
|
17
|
+
# Ignore scratch file
|
|
18
|
+
.bonz
|
|
19
|
+
.save
|
|
20
|
+
# Ignore mac specific junk
|
|
21
|
+
.DS_Store
|
|
22
|
+
|
|
23
|
+
# Temporary Notes file
|
|
24
|
+
Notes.md
|
|
25
|
+
TODO
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[DESIGN]
|
|
2
|
+
|
|
3
|
+
# Maximum number of return statements in a function (see R0915)
|
|
4
|
+
max-returns = 10
|
|
5
|
+
|
|
6
|
+
# Maximum number of boolean expressions in an if statement (see R0916).
|
|
7
|
+
max-bool-expr = 8
|
|
8
|
+
|
|
9
|
+
# Minimum number of public methods for a class (see R0903).
|
|
10
|
+
min-public-methods = 0
|
|
11
|
+
|
|
12
|
+
[FORMAT]
|
|
13
|
+
|
|
14
|
+
# Maximum number of characters on a single line.
|
|
15
|
+
max-line-length=120
|
|
16
|
+
|
|
17
|
+
[MESSAGES CONTROL]
|
|
18
|
+
|
|
19
|
+
# Disable the message, report, category or checker with the given id(s). You
|
|
20
|
+
# can either give multiple identifiers separated by comma (,) or put this
|
|
21
|
+
# option multiple times (only on the command line, not in the configuration
|
|
22
|
+
# file where it should appear only once). You can also use "--disable=all" to
|
|
23
|
+
# disable everything first and then re-enable specific checks. For example, if
|
|
24
|
+
# you want to run only the similarities checker, you can use "--disable=all
|
|
25
|
+
# --enable=similarities". If you want to run only the classes checker, but have
|
|
26
|
+
# no Warning level messages displayed, use "--disable=all --enable=classes
|
|
27
|
+
# --disable=W".
|
|
28
|
+
disable=raw-checker-failed,
|
|
29
|
+
bad-inline-option,
|
|
30
|
+
locally-disabled,
|
|
31
|
+
file-ignored,
|
|
32
|
+
suppressed-message,
|
|
33
|
+
useless-suppression,
|
|
34
|
+
deprecated-pragma,
|
|
35
|
+
use-symbolic-message-instead,
|
|
36
|
+
use-implicit-booleaness-not-comparison-to-string,
|
|
37
|
+
use-implicit-booleaness-not-comparison-to-zero,
|
|
38
|
+
too-few-public-methods,
|
|
39
|
+
unnecessary-ellipsis,
|
|
40
|
+
unnecessary-lambda-assignment
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
[VARIABLES]
|
|
44
|
+
|
|
45
|
+
# List of names allowed to shadow builtins
|
|
46
|
+
allowed-redefined-builtins=map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
functorial-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-present Christopher R. Genovese
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: functorial
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Functional Programming concepts playground
|
|
5
|
+
Author-email: "Christopher R. Genovese" <genovese@cmu.edu>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Programming Language :: Python
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
14
|
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# functorial
|
|
19
|
+
|
|
20
|
+
Functional-programming concepts for Python: typeclasses (Functor, Applicative,
|
|
21
|
+
Monad, Foldable, Traversable, Alternative, ...), standard FP data types
|
|
22
|
+
(`Maybe`, `Either`, `List`, `Dict`, `NTuple`, trees, `IO`, `Reader`, `Writer`,
|
|
23
|
+
`State`), and a profunctor-based optics library (lenses, prisms, traversals,
|
|
24
|
+
folds, isos, and their indexed variants).
|
|
25
|
+
|
|
26
|
+
This is an evolving, educational project — the API is still settling and not
|
|
27
|
+
everything on the roadmap is implemented yet. Feedback and issues are welcome.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install functorial
|
|
33
|
+
# or
|
|
34
|
+
uv add functorial
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Requires Python 3.12+.
|
|
38
|
+
|
|
39
|
+
## Quick taste
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from functorial.maybe import Some, Nothing
|
|
43
|
+
from functorial.optics.lens import lens
|
|
44
|
+
from functorial.optics.getter import view
|
|
45
|
+
from functorial.optics.setter import over
|
|
46
|
+
|
|
47
|
+
# Maybe: a Functor/Applicative/Monad/Traversable
|
|
48
|
+
Some(3).map(lambda x: x + 1) # Some(4)
|
|
49
|
+
Nothing().map(lambda x: x + 1) # Nothing()
|
|
50
|
+
|
|
51
|
+
# Optics: compose lenses with @, act on them with view/over
|
|
52
|
+
fst = lens(lambda s: s[0], lambda s, b: (b, s[1]))
|
|
53
|
+
snd = lens(lambda s: s[1], lambda s, b: (s[0], b))
|
|
54
|
+
|
|
55
|
+
view(fst)((1, 2)) # 1
|
|
56
|
+
over(fst @ snd, str)(((1, 2), 3)) # ((1, '2'), 3)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Development
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
uv sync
|
|
63
|
+
uv run pytest
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## License
|
|
67
|
+
|
|
68
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# functorial
|
|
2
|
+
|
|
3
|
+
Functional-programming concepts for Python: typeclasses (Functor, Applicative,
|
|
4
|
+
Monad, Foldable, Traversable, Alternative, ...), standard FP data types
|
|
5
|
+
(`Maybe`, `Either`, `List`, `Dict`, `NTuple`, trees, `IO`, `Reader`, `Writer`,
|
|
6
|
+
`State`), and a profunctor-based optics library (lenses, prisms, traversals,
|
|
7
|
+
folds, isos, and their indexed variants).
|
|
8
|
+
|
|
9
|
+
This is an evolving, educational project — the API is still settling and not
|
|
10
|
+
everything on the roadmap is implemented yet. Feedback and issues are welcome.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install functorial
|
|
16
|
+
# or
|
|
17
|
+
uv add functorial
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Requires Python 3.12+.
|
|
21
|
+
|
|
22
|
+
## Quick taste
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from functorial.maybe import Some, Nothing
|
|
26
|
+
from functorial.optics.lens import lens
|
|
27
|
+
from functorial.optics.getter import view
|
|
28
|
+
from functorial.optics.setter import over
|
|
29
|
+
|
|
30
|
+
# Maybe: a Functor/Applicative/Monad/Traversable
|
|
31
|
+
Some(3).map(lambda x: x + 1) # Some(4)
|
|
32
|
+
Nothing().map(lambda x: x + 1) # Nothing()
|
|
33
|
+
|
|
34
|
+
# Optics: compose lenses with @, act on them with view/over
|
|
35
|
+
fst = lens(lambda s: s[0], lambda s, b: (b, s[1]))
|
|
36
|
+
snd = lens(lambda s: s[1], lambda s, b: (s[0], b))
|
|
37
|
+
|
|
38
|
+
view(fst)((1, 2)) # 1
|
|
39
|
+
over(fst @ snd, str)(((1, 2), 3)) # ((1, '2'), 3)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Development
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
uv sync
|
|
46
|
+
uv run pytest
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
|
|
51
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "functorial"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "Functional Programming concepts playground"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Christopher R. Genovese", email = "genovese@cmu.edu" }
|
|
10
|
+
]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 4 - Beta",
|
|
13
|
+
"Programming Language :: Python",
|
|
14
|
+
"Programming Language :: Python :: 3.12",
|
|
15
|
+
"Programming Language :: Python :: 3.13",
|
|
16
|
+
"Programming Language :: Python :: 3.14",
|
|
17
|
+
"Programming Language :: Python :: Implementation :: CPython",
|
|
18
|
+
"Programming Language :: Python :: Implementation :: PyPy",
|
|
19
|
+
]
|
|
20
|
+
dependencies = []
|
|
21
|
+
|
|
22
|
+
[dependency-groups]
|
|
23
|
+
dev = [
|
|
24
|
+
"hypothesis>=6.118.0",
|
|
25
|
+
"pytest>=8.3.3",
|
|
26
|
+
"ruff>=0.7.2",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires = ["hatchling"]
|
|
31
|
+
build-backend = "hatchling.build"
|
|
32
|
+
|
|
33
|
+
[tool.hatch.version]
|
|
34
|
+
path = "src/functorial/__about__.py"
|
|
35
|
+
|
|
36
|
+
[[tool.hatch.envs.all.matrix]]
|
|
37
|
+
python = ["3.12", "3.13", "3.14"]
|
|
38
|
+
|
|
39
|
+
[tool.black]
|
|
40
|
+
target-version = ["py312"]
|
|
41
|
+
line-length = 120
|
|
42
|
+
skip-string-normalization = true
|
|
43
|
+
|
|
44
|
+
[tool.ruff]
|
|
45
|
+
target-version = "py312"
|
|
46
|
+
line-length = 120
|
|
47
|
+
|
|
48
|
+
lint.select = [
|
|
49
|
+
"A",
|
|
50
|
+
"ARG",
|
|
51
|
+
"B",
|
|
52
|
+
"C",
|
|
53
|
+
"DTZ",
|
|
54
|
+
"E",
|
|
55
|
+
"EM",
|
|
56
|
+
"F",
|
|
57
|
+
"FBT",
|
|
58
|
+
"I",
|
|
59
|
+
"ICN",
|
|
60
|
+
"ISC",
|
|
61
|
+
"N",
|
|
62
|
+
"PLC",
|
|
63
|
+
"PLE",
|
|
64
|
+
"PLR",
|
|
65
|
+
"PLW",
|
|
66
|
+
"Q",
|
|
67
|
+
"RUF",
|
|
68
|
+
"S",
|
|
69
|
+
"T",
|
|
70
|
+
"TID",
|
|
71
|
+
"UP",
|
|
72
|
+
"W",
|
|
73
|
+
"YTT",
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
lint.ignore = [
|
|
77
|
+
# My ignores
|
|
78
|
+
"I001", "EM101", "Q000", "E501", "N999", # Ruff doesn't support "E241", "E272", "E302", "E305", "W504",
|
|
79
|
+
# Allow non-abstract empty methods in abstract base classes
|
|
80
|
+
"B027",
|
|
81
|
+
# Allow boolean positional values in function calls, like `dict.get(... True)`
|
|
82
|
+
"FBT003",
|
|
83
|
+
# Ignore checks for possible passwords
|
|
84
|
+
"S105", "S106", "S107",
|
|
85
|
+
# Ignore complexity
|
|
86
|
+
"C901", "PLR0911", "PLR0912", "PLR0913", "PLR0915",
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
lint.unfixable = [
|
|
90
|
+
# Don't touch unused imports
|
|
91
|
+
"F401",
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
[tool.ruff.lint.per-file-ignores]
|
|
95
|
+
# Tests can use magic values, assertions, and relative imports
|
|
96
|
+
"tests/**/*" = ["PLR2004", "S101", "TID252"]
|
|
97
|
+
|
|
98
|
+
[tool.ruff.lint.isort]
|
|
99
|
+
section-order = ["future", "standard-library", "first-party", "local-folder", "third-party"]
|
|
100
|
+
lines-between-types = 1
|
|
101
|
+
lines-after-imports = 1
|
|
102
|
+
|
|
103
|
+
[tool.pylint]
|
|
104
|
+
max-line-length = 120
|
|
105
|
+
max-bool-expr = 8
|
|
106
|
+
min-public-methods = 0
|
|
107
|
+
allowed-redefined-builtins = ["map"]
|
|
108
|
+
disable = [
|
|
109
|
+
"raw-checker-failed",
|
|
110
|
+
"bad-inline-option",
|
|
111
|
+
"locally-disabled",
|
|
112
|
+
"file-ignored",
|
|
113
|
+
"suppressed-message",
|
|
114
|
+
"useless-suppression",
|
|
115
|
+
"deprecated-pragma",
|
|
116
|
+
"use-symbolic-message-instead",
|
|
117
|
+
"use-implicit-booleaness-not-comparison-to-string",
|
|
118
|
+
"use-implicit-booleaness-not-comparison-to-zero",
|
|
119
|
+
"too-few-public-methods",
|
|
120
|
+
"unnecessary-ellipsis",
|
|
121
|
+
"unnecessary-lambda-assignment"
|
|
122
|
+
]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
__all__ = [
|
|
2
|
+
'applicative',
|
|
3
|
+
'bicofunctor',
|
|
4
|
+
'bifunctor',
|
|
5
|
+
'bitraversable',
|
|
6
|
+
'cofunctor',
|
|
7
|
+
'const',
|
|
8
|
+
'dict',
|
|
9
|
+
'either',
|
|
10
|
+
'foldable',
|
|
11
|
+
'functor',
|
|
12
|
+
'identity',
|
|
13
|
+
'list',
|
|
14
|
+
'maybe',
|
|
15
|
+
'monad',
|
|
16
|
+
'monoids',
|
|
17
|
+
'ntuple',
|
|
18
|
+
'pair',
|
|
19
|
+
'profunctor',
|
|
20
|
+
'reader',
|
|
21
|
+
'set',
|
|
22
|
+
'state',
|
|
23
|
+
'traversable',
|
|
24
|
+
'trees',
|
|
25
|
+
'functions',
|
|
26
|
+
'ops',
|
|
27
|
+
'optics',
|
|
28
|
+
'utils',
|
|
29
|
+
]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Helper module to load all the names into the namespace
|
|
3
|
+
#
|
|
4
|
+
# Do 'from FP.all import *' to load all the needed objects.
|
|
5
|
+
#
|
|
6
|
+
# ruff: noqa: F401, F403, F405
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from . import monoids
|
|
11
|
+
from . import optics
|
|
12
|
+
|
|
13
|
+
from .alternative import *
|
|
14
|
+
from .applicative import *
|
|
15
|
+
from .bicofunctor import *
|
|
16
|
+
from .bifunctor import *
|
|
17
|
+
from .cofunctor import *
|
|
18
|
+
from .foldable import *
|
|
19
|
+
from .functor import *
|
|
20
|
+
from .monad import *
|
|
21
|
+
from .profunctor import *
|
|
22
|
+
from .traversable import *
|
|
23
|
+
|
|
24
|
+
from .const import *
|
|
25
|
+
from .either import *
|
|
26
|
+
from .identity import *
|
|
27
|
+
from .list import *
|
|
28
|
+
from .maybe import *
|
|
29
|
+
from .ntuple import *
|
|
30
|
+
from .pair import *
|
|
31
|
+
|
|
32
|
+
from .dict import *
|
|
33
|
+
from .monoids import Monoid, munit, mcombine
|
|
34
|
+
from .reader import *
|
|
35
|
+
from .set import *
|
|
36
|
+
from .state import *
|
|
37
|
+
from .trees import *
|
|
38
|
+
from .writer import *
|
|
39
|
+
|
|
40
|
+
from .functions import *
|
|
41
|
+
from .io import *
|
|
42
|
+
from .ops import *
|
|
43
|
+
from .utils import *
|
|
44
|
+
from .wrappers import *
|
|
45
|
+
|
|
46
|
+
from .pair import pair # More powerful version over .functions.pair
|
|
47
|
+
|
|
48
|
+
from .optics.all import *
|
|
49
|
+
|
|
50
|
+
#
|
|
51
|
+
# Conveniences
|
|
52
|
+
#
|
|
53
|
+
|
|
54
|
+
c = compose
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# trait Applicative f => Alternative (f : Type -> Type) where
|
|
2
|
+
# empty : f a
|
|
3
|
+
# alt : f a -> f a -> f a
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from abc import abstractmethod
|
|
8
|
+
from typing import Protocol
|
|
9
|
+
|
|
10
|
+
from .applicative import Applicative
|
|
11
|
+
|
|
12
|
+
__all__ = ['Alternative', 'alt', 'guard']
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
#
|
|
16
|
+
# Alternative as a mixin
|
|
17
|
+
#
|
|
18
|
+
|
|
19
|
+
# ATTN: Should this have a type parameter or be handled like Applicative?
|
|
20
|
+
|
|
21
|
+
class Alternative[A](Applicative, Protocol):
|
|
22
|
+
@classmethod
|
|
23
|
+
def empty(cls) -> Alternative[A]:
|
|
24
|
+
raise NotImplementedError
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def alt(self, fb: Alternative[A]) -> Alternative[A]:
|
|
28
|
+
...
|
|
29
|
+
|
|
30
|
+
def alt[A](fa: Alternative[A], fb: Alternative[A]) -> Alternative[A]:
|
|
31
|
+
return fa.alt(fb)
|
|
32
|
+
|
|
33
|
+
def guard(f: type[Alternative], condition: bool) -> Alternative[tuple[()]]: # ATTN: type Unit = tuple[()]
|
|
34
|
+
return f.unit() if condition else f.empty()
|
|
35
|
+
|
|
36
|
+
# ATTN: Include some and many? Can we implement them?
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# trait Functor f => Applicative (f : Type -> Type) where
|
|
2
|
+
# pure : a -> f a
|
|
3
|
+
# map2 : (a -> b -> c) -> f a -> f b -> f c -- lift2 := map2 h
|
|
4
|
+
# ap : f (a -> b) -> f a -> f b
|
|
5
|
+
#
|
|
6
|
+
# unit : f Unit -- Unit equiv ()
|
|
7
|
+
# combine : f a -> f b -> f (a, b)
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from abc import abstractmethod
|
|
12
|
+
from collections.abc import Callable
|
|
13
|
+
from typing import Protocol, runtime_checkable
|
|
14
|
+
|
|
15
|
+
from .functor import Functor, map # pylint: disable=redefined-builtin
|
|
16
|
+
from .functions import compose, const, curry, identity, pair, fn_eval, eval_with
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
'Applicative', 'map2', 'combine', 'pure',
|
|
20
|
+
'ap', 'lift2', 'ap_first', 'ap_second', 'rev_ap',
|
|
21
|
+
'when', 'unless', # ATTN: needed?
|
|
22
|
+
'IdentityA',
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
#
|
|
27
|
+
# Applicative as a mixin
|
|
28
|
+
#
|
|
29
|
+
|
|
30
|
+
@runtime_checkable
|
|
31
|
+
class Applicative(Functor, Protocol):
|
|
32
|
+
@classmethod
|
|
33
|
+
def pure(cls, a):
|
|
34
|
+
raise NotImplementedError
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def map2(self, g, fb):
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def unit(cls):
|
|
42
|
+
return cls.pure( () )
|
|
43
|
+
|
|
44
|
+
def combine(self, fb):
|
|
45
|
+
return self.map2(pair, fb)
|
|
46
|
+
|
|
47
|
+
def ap(self, fb):
|
|
48
|
+
return self.map2(fn_eval, fb)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def map2(g, fa, fb):
|
|
52
|
+
return fa.map2(g, fb)
|
|
53
|
+
|
|
54
|
+
def combine(fa, fb):
|
|
55
|
+
return fa.combine(fb)
|
|
56
|
+
|
|
57
|
+
def pure(fa, a):
|
|
58
|
+
return fa.pure(a)
|
|
59
|
+
|
|
60
|
+
def ap(fa_to_b: Applicative | Callable, fa: Applicative, *fs: Applicative, auto_curry=True) -> Applicative:
|
|
61
|
+
if not isinstance(fa_to_b, Applicative):
|
|
62
|
+
if auto_curry:
|
|
63
|
+
fa_to_b = fa.pure(curry(fa_to_b))
|
|
64
|
+
else:
|
|
65
|
+
fa_to_b = fa.pure(fa_to_b)
|
|
66
|
+
# elif auto_curry:
|
|
67
|
+
# fa_to_b = fa_to_b.map(curry) # ATTN: PROVISIONAL
|
|
68
|
+
|
|
69
|
+
fb = fa_to_b.ap(fa) # type: ignore
|
|
70
|
+
for fx in fs:
|
|
71
|
+
fb = fb.ap(fx)
|
|
72
|
+
return fb
|
|
73
|
+
|
|
74
|
+
# ATTN: by our emerging convention, this should be called map2_, though this name is good too
|
|
75
|
+
def lift2[A, B, C](f: Callable[[A, B], C]):
|
|
76
|
+
"""Lifts a two-argument function to a mapping of Applicatives.
|
|
77
|
+
|
|
78
|
+
This is just the partial application map2(f, _, __).
|
|
79
|
+
The applicatives should be the same type (technically
|
|
80
|
+
one should be a subclass of the other).
|
|
81
|
+
|
|
82
|
+
"""
|
|
83
|
+
def liftA2(fa: Applicative, fb: Applicative) -> Applicative:
|
|
84
|
+
if not issubclass(fa.__class__, fb.__class__) and not issubclass(fb.__class__, fa.__class__):
|
|
85
|
+
raise TypeError('lift2(f) should be applied to compatible applicatives.')
|
|
86
|
+
return fa.map2(f, fb)
|
|
87
|
+
|
|
88
|
+
return liftA2
|
|
89
|
+
|
|
90
|
+
map2_ = lift2 # Alias for lift2 that matches our naming convention
|
|
91
|
+
|
|
92
|
+
def rev_ap(fa: Applicative, fa_to_b: Applicative | Callable, auto_curry=True) -> Applicative:
|
|
93
|
+
"""A variant of ap with the arguments reversed and effects resolved in the order given.
|
|
94
|
+
|
|
95
|
+
Note that rev_ap differs from flip(ap) in the order in which effects
|
|
96
|
+
are resolved. The latter would just remap the argument order into ap,
|
|
97
|
+
but this resolves fa then fa_to_b.
|
|
98
|
+
|
|
99
|
+
Unlike ap, this only takes two arguments, but it does do automatic currying
|
|
100
|
+
if auto_curry is True, which is the default.
|
|
101
|
+
|
|
102
|
+
Returns the resulting applicative.
|
|
103
|
+
|
|
104
|
+
"""
|
|
105
|
+
if not isinstance(fa_to_b, Applicative):
|
|
106
|
+
if auto_curry:
|
|
107
|
+
fa_to_b = fa.pure(curry(fa_to_b))
|
|
108
|
+
else:
|
|
109
|
+
fa_to_b = fa.pure(fa_to_b)
|
|
110
|
+
|
|
111
|
+
return fa.map2(eval_with, fa_to_b)
|
|
112
|
+
|
|
113
|
+
# (<*) : f a -> f b -> f a
|
|
114
|
+
def ap_first(fa: Applicative, fb: Applicative) -> Applicative:
|
|
115
|
+
"Sequence actions, disgarding the value of the second argument."
|
|
116
|
+
return fa.map2(lambda a, b: a, fb)
|
|
117
|
+
|
|
118
|
+
# (*>) : f a -> f b -> f b
|
|
119
|
+
def ap_second(fa: Applicative, fb: Applicative) -> Applicative:
|
|
120
|
+
"Sequence actions, disgarding the value of the first argument."
|
|
121
|
+
return ap(map(compose(identity, const), fa), fb)
|
|
122
|
+
|
|
123
|
+
# ATTN: implement when and unless here? Are they useful at all for us, as we don't need it for??
|
|
124
|
+
# when : Applicative f => Bool -> f () -> f ()
|
|
125
|
+
def when(f: type[Applicative], condition: bool, true_case: Applicative) -> Applicative:
|
|
126
|
+
return map(const(()), true_case) if condition else f.pure(())
|
|
127
|
+
|
|
128
|
+
# unless : Applicative f => Bool -> f () -> f ()
|
|
129
|
+
def unless(f: type[Applicative], condition: bool, false_case: Applicative) -> Applicative:
|
|
130
|
+
return f.pure(()) if condition else map(const(()), false_case)
|
|
131
|
+
|
|
132
|
+
# A copy of the Identity Functor that is only an Applicative
|
|
133
|
+
# This is useful as a default applicative in infrastructure
|
|
134
|
+
# modules that would lead to circularity if loading Identity
|
|
135
|
+
# module. See also IdentityM in case a default Monad is needed.
|
|
136
|
+
|
|
137
|
+
class IdentityA[A](Applicative):
|
|
138
|
+
"""A default Applicative that mimics Identity without Monad or Traversable.
|
|
139
|
+
|
|
140
|
+
This is useful in defaults only infrastructure modules in this package,
|
|
141
|
+
like Monad and Traversable, that Identity actually loads. Users should
|
|
142
|
+
not use this explicitly.
|
|
143
|
+
|
|
144
|
+
"""
|
|
145
|
+
__match_args__ = ('_value',)
|
|
146
|
+
|
|
147
|
+
def __init__(self, x: A):
|
|
148
|
+
self._value = x
|
|
149
|
+
|
|
150
|
+
def __str__(self):
|
|
151
|
+
return f'IdentityA {self._value}'
|
|
152
|
+
|
|
153
|
+
def __repr__(self):
|
|
154
|
+
return f'IdentityA({self._value})'
|
|
155
|
+
|
|
156
|
+
@classmethod
|
|
157
|
+
def run(cls, fa: IdentityA[A]) -> A:
|
|
158
|
+
return fa._value
|
|
159
|
+
|
|
160
|
+
def map[B](self, g: Callable[[A], B]) -> IdentityA[B]:
|
|
161
|
+
return IdentityA(g(self._value))
|
|
162
|
+
|
|
163
|
+
@classmethod
|
|
164
|
+
def pure(cls, x: A) -> IdentityA[A]:
|
|
165
|
+
return IdentityA(x)
|
|
166
|
+
|
|
167
|
+
def map2[B, C](self, g: Callable[[A, B], C], fb: IdentityA[B]) -> IdentityA[C]:
|
|
168
|
+
return IdentityA(g(self._value, fb._value))
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
# Incomplete but working
|
|
3
|
+
|
|
4
|
+
from .applicative import Applicative
|
|
5
|
+
from .functions import flip
|
|
6
|
+
|
|
7
|
+
def Backwards(f: type[Applicative]):
|
|
8
|
+
class Backward_f(f):
|
|
9
|
+
"A Backward version of the Applicative f."
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def pure(cls, a):
|
|
13
|
+
return cls(f.pure(a))
|
|
14
|
+
|
|
15
|
+
def map2(self, g, fb):
|
|
16
|
+
return Backward_f(f(fb).map2(flip(g), f(self)))
|
|
17
|
+
|
|
18
|
+
return Backward_f
|