stolas 0.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.
- stolas-0.1.0/LICENSE.md +21 -0
- stolas-0.1.0/PKG-INFO +120 -0
- stolas-0.1.0/README.md +95 -0
- stolas-0.1.0/pyproject.toml +49 -0
- stolas-0.1.0/setup.cfg +4 -0
- stolas-0.1.0/src/stolas/__init__.py +0 -0
- stolas-0.1.0/src/stolas/logic/__init__.py +56 -0
- stolas-0.1.0/src/stolas/logic/access.py +44 -0
- stolas-0.1.0/src/stolas/logic/access.pyi +17 -0
- stolas-0.1.0/src/stolas/logic/collection.py +137 -0
- stolas-0.1.0/src/stolas/logic/collection.pyi +54 -0
- stolas-0.1.0/src/stolas/logic/common.py +66 -0
- stolas-0.1.0/src/stolas/logic/common.pyi +25 -0
- stolas-0.1.0/src/stolas/logic/flow.py +37 -0
- stolas-0.1.0/src/stolas/logic/flow.pyi +16 -0
- stolas-0.1.0/src/stolas/logic/placeholder.py +296 -0
- stolas-0.1.0/src/stolas/logic/placeholder.pyi +81 -0
- stolas-0.1.0/src/stolas/logic/predicates.py +107 -0
- stolas-0.1.0/src/stolas/logic/predicates.pyi +19 -0
- stolas-0.1.0/src/stolas/logic/utils.py +66 -0
- stolas-0.1.0/src/stolas/logic/utils.pyi +27 -0
- stolas-0.1.0/src/stolas/mypy_plugin.py +62 -0
- stolas-0.1.0/src/stolas/operand/__init__.py +30 -0
- stolas-0.1.0/src/stolas/operand/arity.py +213 -0
- stolas-0.1.0/src/stolas/operand/arity.pyi +85 -0
- stolas-0.1.0/src/stolas/operand/cases.py +146 -0
- stolas-0.1.0/src/stolas/operand/cases.pyi +19 -0
- stolas-0.1.0/src/stolas/operand/concurrent.py +31 -0
- stolas-0.1.0/src/stolas/operand/concurrent.pyi +14 -0
- stolas-0.1.0/src/stolas/operand/ops.py +37 -0
- stolas-0.1.0/src/stolas/operand/ops.pyi +21 -0
- stolas-0.1.0/src/stolas/operand/safe.py +131 -0
- stolas-0.1.0/src/stolas/operand/safe.pyi +24 -0
- stolas-0.1.0/src/stolas/py.typed +0 -0
- stolas-0.1.0/src/stolas/struct/__init__.py +6 -0
- stolas-0.1.0/src/stolas/struct/struct.py +149 -0
- stolas-0.1.0/src/stolas/struct/struct.pyi +15 -0
- stolas-0.1.0/src/stolas/struct/trait.py +258 -0
- stolas-0.1.0/src/stolas/struct/trait.pyi +71 -0
- stolas-0.1.0/src/stolas/types/__init__.py +21 -0
- stolas-0.1.0/src/stolas/types/effect.py +79 -0
- stolas-0.1.0/src/stolas/types/effect.pyi +25 -0
- stolas-0.1.0/src/stolas/types/many.py +126 -0
- stolas-0.1.0/src/stolas/types/many.pyi +31 -0
- stolas-0.1.0/src/stolas/types/option.py +127 -0
- stolas-0.1.0/src/stolas/types/option.pyi +45 -0
- stolas-0.1.0/src/stolas/types/result.py +146 -0
- stolas-0.1.0/src/stolas/types/result.pyi +50 -0
- stolas-0.1.0/src/stolas/types/validated.py +122 -0
- stolas-0.1.0/src/stolas/types/validated.pyi +43 -0
- stolas-0.1.0/src/stolas.egg-info/PKG-INFO +120 -0
- stolas-0.1.0/src/stolas.egg-info/SOURCES.txt +53 -0
- stolas-0.1.0/src/stolas.egg-info/dependency_links.txt +1 -0
- stolas-0.1.0/src/stolas.egg-info/requires.txt +5 -0
- stolas-0.1.0/src/stolas.egg-info/top_level.txt +1 -0
stolas-0.1.0/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Stolas Contributors
|
|
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.
|
stolas-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: stolas
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The strict, multi-paradigm framework enabling pure functional patterns in Python.
|
|
5
|
+
Author: Stolas Contributors
|
|
6
|
+
Project-URL: Homepage, https://github.com/eaksit-bua/stolas
|
|
7
|
+
Project-URL: Repository, https://github.com/eaksit-bua/stolas
|
|
8
|
+
Project-URL: Documentation, https://github.com/eaksit-bua/stolas#readme
|
|
9
|
+
Keywords: functional,monads,immutable,struct,trait
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE.md
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: mypy>=1.19.1; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest>=9.0.2; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-cov>=7.0.0; extra == "dev"
|
|
25
|
+
|
|
26
|
+
# 🦉 Stolas
|
|
27
|
+
|
|
28
|
+
**The strict, multi-paradigm framework enabling pure functional patterns in Python.**
|
|
29
|
+
|
|
30
|
+
*Wisdom through pure functional patterns* — Safe separation of data and behavior with monadic safety, immutability, and type-safe composition.
|
|
31
|
+
|
|
32
|
+
## 🦉 The S-T-O-L-A-S Framework
|
|
33
|
+
|
|
34
|
+
### **S**truct
|
|
35
|
+
Fast, immutable data classes with `@struct` and polymorphic `@trait` for behavior dispatch.
|
|
36
|
+
|
|
37
|
+
### **T**ypes
|
|
38
|
+
Safe monadic containers: `Result`, `Option`, `Validated`, `Effect`, `Many`
|
|
39
|
+
|
|
40
|
+
### **O**perands
|
|
41
|
+
Powerful decorators: `@ops`, `@cases`, `@binary`, `@as_result`, `concurrent()`
|
|
42
|
+
|
|
43
|
+
### **L**ogic
|
|
44
|
+
Ergonomic functional combinators: `get`, `at`, `where`, `apply`, `_` placeholder, and 20+ utilities
|
|
45
|
+
|
|
46
|
+
### **A-S**
|
|
47
|
+
*(Reserved for future expansion)*
|
|
48
|
+
|
|
49
|
+
## 🦉 Key Features
|
|
50
|
+
|
|
51
|
+
- ✅ **Strictness**: Runtime type checking + `__slots__` for memory efficiency
|
|
52
|
+
- ✅ **Sealed ADTs**: `@cases` decorator with pattern matching and monadic compatibility
|
|
53
|
+
- ✅ **Functional Composition**: Pipeline chaining with `>>`
|
|
54
|
+
- ✅ **Async Concurrency**: Parallel workflows with `concurrent()`
|
|
55
|
+
- ✅ **Polymorphism**: Trait-based dispatch for decoupled behavior
|
|
56
|
+
- ✅ **Type Safety**: Full `mypy --strict` compatibility
|
|
57
|
+
|
|
58
|
+
## 🦉 Installation
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install stolas
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 🦉 Quick Example
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from stolas.struct import struct
|
|
68
|
+
from stolas.types import Many
|
|
69
|
+
from stolas.operand import binary, as_result, ops
|
|
70
|
+
from stolas.logic import where, apply, _
|
|
71
|
+
|
|
72
|
+
# Immutable data
|
|
73
|
+
@struct
|
|
74
|
+
class User:
|
|
75
|
+
id: int
|
|
76
|
+
name: str
|
|
77
|
+
email: str
|
|
78
|
+
|
|
79
|
+
# Safe, curried operations
|
|
80
|
+
@ops(binary, as_result)
|
|
81
|
+
def divide(a: int, b: int) -> float:
|
|
82
|
+
return a / b
|
|
83
|
+
|
|
84
|
+
# Functional pipelines with placeholder
|
|
85
|
+
users = Many([
|
|
86
|
+
User(1, "Alice", "alice@example.com"),
|
|
87
|
+
User(2, "Bob", "bob@example.com"),
|
|
88
|
+
])
|
|
89
|
+
|
|
90
|
+
result = users >> where(_.id > 1) >> apply(_.name) # Many(["Bob"])
|
|
91
|
+
|
|
92
|
+
# Monadic safety
|
|
93
|
+
divide(10)(2) # Ok(5.0)
|
|
94
|
+
divide(10)(0) # Error(ZeroDivisionError(...))
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 🦉 Documentation
|
|
98
|
+
|
|
99
|
+
For detailed documentation, see the **[docs/](docs/)** directory:
|
|
100
|
+
|
|
101
|
+
- **[Struct & Trait](docs/struct.md)** - Polymorphism (`@trait`) and immutable data (`@struct`)
|
|
102
|
+
- **[Monadic Types](docs/types.md)** - `Result`, `Option`, `Validated`, `Effect`, `Many`
|
|
103
|
+
- **[Operands](docs/operands.md)** - Decorators `@cases`, `@safe`, and Concurrency
|
|
104
|
+
- **[Logic Reference](docs/logic.md)** - Combinators and Placeholder (`_`)
|
|
105
|
+
|
|
106
|
+
## 🦉 Testing
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Run tests
|
|
110
|
+
python -m pytest tests/
|
|
111
|
+
|
|
112
|
+
# Type checking
|
|
113
|
+
mypy src/stolas --strict
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Status:** 🦉 739 tests passing • 100% coverage • 100% mypy strict compliance
|
|
117
|
+
|
|
118
|
+
## 🦉 License
|
|
119
|
+
|
|
120
|
+
MIT License
|
stolas-0.1.0/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# 🦉 Stolas
|
|
2
|
+
|
|
3
|
+
**The strict, multi-paradigm framework enabling pure functional patterns in Python.**
|
|
4
|
+
|
|
5
|
+
*Wisdom through pure functional patterns* — Safe separation of data and behavior with monadic safety, immutability, and type-safe composition.
|
|
6
|
+
|
|
7
|
+
## 🦉 The S-T-O-L-A-S Framework
|
|
8
|
+
|
|
9
|
+
### **S**truct
|
|
10
|
+
Fast, immutable data classes with `@struct` and polymorphic `@trait` for behavior dispatch.
|
|
11
|
+
|
|
12
|
+
### **T**ypes
|
|
13
|
+
Safe monadic containers: `Result`, `Option`, `Validated`, `Effect`, `Many`
|
|
14
|
+
|
|
15
|
+
### **O**perands
|
|
16
|
+
Powerful decorators: `@ops`, `@cases`, `@binary`, `@as_result`, `concurrent()`
|
|
17
|
+
|
|
18
|
+
### **L**ogic
|
|
19
|
+
Ergonomic functional combinators: `get`, `at`, `where`, `apply`, `_` placeholder, and 20+ utilities
|
|
20
|
+
|
|
21
|
+
### **A-S**
|
|
22
|
+
*(Reserved for future expansion)*
|
|
23
|
+
|
|
24
|
+
## 🦉 Key Features
|
|
25
|
+
|
|
26
|
+
- ✅ **Strictness**: Runtime type checking + `__slots__` for memory efficiency
|
|
27
|
+
- ✅ **Sealed ADTs**: `@cases` decorator with pattern matching and monadic compatibility
|
|
28
|
+
- ✅ **Functional Composition**: Pipeline chaining with `>>`
|
|
29
|
+
- ✅ **Async Concurrency**: Parallel workflows with `concurrent()`
|
|
30
|
+
- ✅ **Polymorphism**: Trait-based dispatch for decoupled behavior
|
|
31
|
+
- ✅ **Type Safety**: Full `mypy --strict` compatibility
|
|
32
|
+
|
|
33
|
+
## 🦉 Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install stolas
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 🦉 Quick Example
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from stolas.struct import struct
|
|
43
|
+
from stolas.types import Many
|
|
44
|
+
from stolas.operand import binary, as_result, ops
|
|
45
|
+
from stolas.logic import where, apply, _
|
|
46
|
+
|
|
47
|
+
# Immutable data
|
|
48
|
+
@struct
|
|
49
|
+
class User:
|
|
50
|
+
id: int
|
|
51
|
+
name: str
|
|
52
|
+
email: str
|
|
53
|
+
|
|
54
|
+
# Safe, curried operations
|
|
55
|
+
@ops(binary, as_result)
|
|
56
|
+
def divide(a: int, b: int) -> float:
|
|
57
|
+
return a / b
|
|
58
|
+
|
|
59
|
+
# Functional pipelines with placeholder
|
|
60
|
+
users = Many([
|
|
61
|
+
User(1, "Alice", "alice@example.com"),
|
|
62
|
+
User(2, "Bob", "bob@example.com"),
|
|
63
|
+
])
|
|
64
|
+
|
|
65
|
+
result = users >> where(_.id > 1) >> apply(_.name) # Many(["Bob"])
|
|
66
|
+
|
|
67
|
+
# Monadic safety
|
|
68
|
+
divide(10)(2) # Ok(5.0)
|
|
69
|
+
divide(10)(0) # Error(ZeroDivisionError(...))
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## 🦉 Documentation
|
|
73
|
+
|
|
74
|
+
For detailed documentation, see the **[docs/](docs/)** directory:
|
|
75
|
+
|
|
76
|
+
- **[Struct & Trait](docs/struct.md)** - Polymorphism (`@trait`) and immutable data (`@struct`)
|
|
77
|
+
- **[Monadic Types](docs/types.md)** - `Result`, `Option`, `Validated`, `Effect`, `Many`
|
|
78
|
+
- **[Operands](docs/operands.md)** - Decorators `@cases`, `@safe`, and Concurrency
|
|
79
|
+
- **[Logic Reference](docs/logic.md)** - Combinators and Placeholder (`_`)
|
|
80
|
+
|
|
81
|
+
## 🦉 Testing
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Run tests
|
|
85
|
+
python -m pytest tests/
|
|
86
|
+
|
|
87
|
+
# Type checking
|
|
88
|
+
mypy src/stolas --strict
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Status:** 🦉 739 tests passing • 100% coverage • 100% mypy strict compliance
|
|
92
|
+
|
|
93
|
+
## 🦉 License
|
|
94
|
+
|
|
95
|
+
MIT License
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "stolas"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "The strict, multi-paradigm framework enabling pure functional patterns in Python."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
authors = [
|
|
8
|
+
{name = "Stolas Contributors"}
|
|
9
|
+
]
|
|
10
|
+
keywords = ["functional", "monads", "immutable", "struct", "trait"]
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 4 - Beta",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
"Programming Language :: Python :: 3.13",
|
|
18
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
19
|
+
"Typing :: Typed",
|
|
20
|
+
]
|
|
21
|
+
dependencies = []
|
|
22
|
+
|
|
23
|
+
[project.optional-dependencies]
|
|
24
|
+
dev = [
|
|
25
|
+
"mypy>=1.19.1",
|
|
26
|
+
"pytest>=9.0.2",
|
|
27
|
+
"pytest-cov>=7.0.0",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/eaksit-bua/stolas"
|
|
32
|
+
Repository = "https://github.com/eaksit-bua/stolas"
|
|
33
|
+
Documentation = "https://github.com/eaksit-bua/stolas#readme"
|
|
34
|
+
|
|
35
|
+
[build-system]
|
|
36
|
+
requires = ["setuptools>=61.0,<75.0"]
|
|
37
|
+
build-backend = "setuptools.build_meta"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
where = ["src"]
|
|
41
|
+
|
|
42
|
+
[tool.coverage.run]
|
|
43
|
+
omit = ["src/stolas/mypy_plugin.py"]
|
|
44
|
+
|
|
45
|
+
[tool.coverage.report]
|
|
46
|
+
exclude_lines = [
|
|
47
|
+
"pragma: no cover",
|
|
48
|
+
"if TYPE_CHECKING:",
|
|
49
|
+
]
|
stolas-0.1.0/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Logic module: functional utilities and helpers."""
|
|
2
|
+
|
|
3
|
+
from stolas.logic.access import at, call, get
|
|
4
|
+
from stolas.logic.collection import (
|
|
5
|
+
apply,
|
|
6
|
+
chain,
|
|
7
|
+
count,
|
|
8
|
+
find,
|
|
9
|
+
first,
|
|
10
|
+
last,
|
|
11
|
+
pair,
|
|
12
|
+
sort,
|
|
13
|
+
where,
|
|
14
|
+
)
|
|
15
|
+
from stolas.logic.common import const, fmt, identity, tap, tee
|
|
16
|
+
from stolas.logic.predicates import both, contains, either, negate
|
|
17
|
+
from stolas.logic.flow import check, strict
|
|
18
|
+
from stolas.logic.placeholder import _
|
|
19
|
+
from stolas.logic.utils import alt, compose, when, wrap
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
# Access
|
|
23
|
+
"get",
|
|
24
|
+
"at",
|
|
25
|
+
"call",
|
|
26
|
+
# Collection
|
|
27
|
+
"chain",
|
|
28
|
+
"where",
|
|
29
|
+
"apply",
|
|
30
|
+
"count",
|
|
31
|
+
"first",
|
|
32
|
+
"last",
|
|
33
|
+
"pair",
|
|
34
|
+
"find",
|
|
35
|
+
"sort",
|
|
36
|
+
# Flow
|
|
37
|
+
"check",
|
|
38
|
+
"strict",
|
|
39
|
+
# Placeholder
|
|
40
|
+
"_",
|
|
41
|
+
# Utils
|
|
42
|
+
"identity",
|
|
43
|
+
"const",
|
|
44
|
+
"tap",
|
|
45
|
+
"tee",
|
|
46
|
+
"fmt",
|
|
47
|
+
"wrap",
|
|
48
|
+
"when",
|
|
49
|
+
"compose",
|
|
50
|
+
"alt",
|
|
51
|
+
# Predicates
|
|
52
|
+
"contains",
|
|
53
|
+
"negate",
|
|
54
|
+
"both",
|
|
55
|
+
"either",
|
|
56
|
+
]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Accessor helpers: get, at, call."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, TypeVar
|
|
4
|
+
|
|
5
|
+
T = TypeVar("T")
|
|
6
|
+
K = TypeVar("K")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get(attr: str) -> Callable[[Any], Any]:
|
|
10
|
+
"""Return a function that gets an attribute from an object.
|
|
11
|
+
|
|
12
|
+
Usage: Ok(user) >> get("name")
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def wrapper(x: Any) -> Any:
|
|
16
|
+
return getattr(x, attr)
|
|
17
|
+
|
|
18
|
+
return wrapper
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def at(key: K) -> Callable[[Any], Any]:
|
|
22
|
+
"""Return a function that gets an item by key/index.
|
|
23
|
+
|
|
24
|
+
Usage: Ok(data) >> at("id")
|
|
25
|
+
Ok(items) >> at(0)
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def wrapper(x: Any) -> Any:
|
|
29
|
+
return x[key]
|
|
30
|
+
|
|
31
|
+
return wrapper
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def call(method: str, *args: Any, **kwargs: Any) -> Callable[[Any], Any]:
|
|
35
|
+
"""Return a function that calls a method on an object.
|
|
36
|
+
|
|
37
|
+
Usage: Ok(s) >> call("upper")
|
|
38
|
+
Ok(s) >> call("replace", "a", "b")
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def wrapper(x: Any) -> Any:
|
|
42
|
+
return getattr(x, method)(*args, **kwargs)
|
|
43
|
+
|
|
44
|
+
return wrapper
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Type stubs for access module."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, TypeVar
|
|
4
|
+
|
|
5
|
+
K = TypeVar("K")
|
|
6
|
+
|
|
7
|
+
def get(attr: str) -> Callable[[Any], Any]:
|
|
8
|
+
"""Get attribute from object."""
|
|
9
|
+
...
|
|
10
|
+
|
|
11
|
+
def at(key: K) -> Callable[[Any], Any]:
|
|
12
|
+
"""Get item by key or index."""
|
|
13
|
+
...
|
|
14
|
+
|
|
15
|
+
def call(method: str, *args: Any, **kwargs: Any) -> Callable[[Any], Any]:
|
|
16
|
+
"""Call method on object."""
|
|
17
|
+
...
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Collection helpers: chain, where, apply, count, first, last, pair, find, sort."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Iterable, TypeVar
|
|
4
|
+
|
|
5
|
+
from stolas.types.many import Many
|
|
6
|
+
from stolas.types.option import Nothing, Option, Some
|
|
7
|
+
|
|
8
|
+
T = TypeVar("T")
|
|
9
|
+
U = TypeVar("U")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def chain(
|
|
13
|
+
func: Callable[[T], Iterable[U]] | Callable[[T], Many[U]],
|
|
14
|
+
) -> Callable[[Many[T]], Many[U]]:
|
|
15
|
+
"""FlatMap helper: maps function over Many items and flattens result.
|
|
16
|
+
|
|
17
|
+
Usage: Many(...) >> chain(_.sub_items)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def wrapper(m: Many[T]) -> Many[U]:
|
|
21
|
+
results: list[U] = []
|
|
22
|
+
for x in m._items:
|
|
23
|
+
res = func(x)
|
|
24
|
+
if isinstance(res, Many):
|
|
25
|
+
results.extend(res.items)
|
|
26
|
+
elif isinstance(res, Iterable):
|
|
27
|
+
results.extend(res)
|
|
28
|
+
else:
|
|
29
|
+
raise TypeError(f"Expected Iterable or Many, got {type(res)}")
|
|
30
|
+
return Many(tuple(results))
|
|
31
|
+
|
|
32
|
+
return wrapper
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def where(predicate: Callable[[T], bool]) -> Callable[[Many[T]], Many[T]]:
|
|
36
|
+
"""Filter helper: keeps items matching predicate.
|
|
37
|
+
|
|
38
|
+
Usage: Many(...) >> where(_ > 10)
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def wrapper(m: Many[T]) -> Many[T]:
|
|
42
|
+
return Many(tuple(x for x in m._items if predicate(x)))
|
|
43
|
+
|
|
44
|
+
return wrapper
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def apply(func: Callable[[T], U]) -> Callable[[Many[T]], Many[U]]:
|
|
48
|
+
"""Map helper: applies function to each item.
|
|
49
|
+
|
|
50
|
+
Usage: Many(...) >> apply(_.upper())
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def wrapper(m: Many[T]) -> Many[U]:
|
|
54
|
+
return Many(tuple(func(x) for x in m._items))
|
|
55
|
+
|
|
56
|
+
return wrapper
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def count() -> Callable[[Many[T]], Some[int]]:
|
|
60
|
+
"""Return count of items wrapped in Some.
|
|
61
|
+
|
|
62
|
+
Usage: Many(...) >> count() # returns Some(N)
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def wrapper(m: Many[T]) -> Some[int]:
|
|
66
|
+
return Some(len(m._items))
|
|
67
|
+
|
|
68
|
+
return wrapper
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def first() -> Callable[[Many[T]], Option[T]]:
|
|
72
|
+
"""Return first item as Some, or Nothing if empty.
|
|
73
|
+
|
|
74
|
+
Usage: Many(...) >> first() # returns Some(x) or Nothing
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def wrapper(m: Many[T]) -> Option[T]:
|
|
78
|
+
if m._items:
|
|
79
|
+
return Some(m._items[0])
|
|
80
|
+
return Nothing
|
|
81
|
+
|
|
82
|
+
return wrapper
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def last() -> Callable[[Many[T]], Option[T]]:
|
|
86
|
+
"""Return last item as Some, or Nothing if empty.
|
|
87
|
+
|
|
88
|
+
Usage: Many(...) >> last() # returns Some(x) or Nothing
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
def wrapper(m: Many[T]) -> Option[T]:
|
|
92
|
+
if m._items:
|
|
93
|
+
return Some(m._items[-1])
|
|
94
|
+
return Nothing
|
|
95
|
+
|
|
96
|
+
return wrapper
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def pair(other: Many[U]) -> Callable[[Many[T]], Many[tuple[T, U]]]:
|
|
100
|
+
"""Zip with another Many collection.
|
|
101
|
+
|
|
102
|
+
Usage: Many([1,2]) >> pair(Many(['a','b'])) # Many([(1,'a'), (2,'b')])
|
|
103
|
+
"""
|
|
104
|
+
|
|
105
|
+
def wrapper(m: Many[T]) -> Many[tuple[T, U]]:
|
|
106
|
+
return Many(tuple(zip(m._items, other._items)))
|
|
107
|
+
|
|
108
|
+
return wrapper
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def find(predicate: Callable[[T], bool]) -> Callable[[Many[T]], Option[T]]:
|
|
112
|
+
"""Find first item matching predicate.
|
|
113
|
+
|
|
114
|
+
Usage: Many(...) >> find(_ == 5) # returns Some(5) or Nothing
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
def wrapper(m: Many[T]) -> Option[T]:
|
|
118
|
+
for x in m._items:
|
|
119
|
+
if predicate(x):
|
|
120
|
+
return Some(x)
|
|
121
|
+
return Nothing
|
|
122
|
+
|
|
123
|
+
return wrapper
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def sort(
|
|
127
|
+
key: Callable[[T], Any] | None = None, reverse: bool = False
|
|
128
|
+
) -> Callable[[Many[T]], Many[T]]:
|
|
129
|
+
"""Return sorted Many.
|
|
130
|
+
|
|
131
|
+
Usage: Many(...) >> sort(key=_.age)
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
def wrapper(m: Many[T]) -> Many[T]:
|
|
135
|
+
return Many(tuple(sorted(m._items, key=key, reverse=reverse))) # type: ignore[arg-type,type-var]
|
|
136
|
+
|
|
137
|
+
return wrapper
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Type stubs for collection module."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Iterable, TypeVar, overload
|
|
4
|
+
from stolas.types.many import Many
|
|
5
|
+
from stolas.types.option import Option, Some
|
|
6
|
+
|
|
7
|
+
T = TypeVar("T")
|
|
8
|
+
U = TypeVar("U")
|
|
9
|
+
|
|
10
|
+
def chain(func: Callable[[Any], Iterable[U]]) -> Callable[[Many[Any]], Many[U]]:
|
|
11
|
+
"""FlatMap over Many items.
|
|
12
|
+
|
|
13
|
+
Works with any function returning Iterable (including Many).
|
|
14
|
+
"""
|
|
15
|
+
...
|
|
16
|
+
|
|
17
|
+
def where(predicate: Callable[[T], bool]) -> Callable[[Many[T]], Many[T]]:
|
|
18
|
+
"""Filter items by predicate."""
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
def apply(func: Callable[[T], U]) -> Callable[[Many[T]], Many[U]]:
|
|
22
|
+
"""Map function over items."""
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
def count() -> Callable[[Many[Any]], Some[int]]:
|
|
26
|
+
"""Count items."""
|
|
27
|
+
...
|
|
28
|
+
|
|
29
|
+
def first() -> Callable[[Many[T]], Option[T]]:
|
|
30
|
+
"""Get first item."""
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
def last() -> Callable[[Many[T]], Option[T]]:
|
|
34
|
+
"""Get last item."""
|
|
35
|
+
...
|
|
36
|
+
|
|
37
|
+
def pair(other: Many[U]) -> Callable[[Many[T]], Many[tuple[T, U]]]:
|
|
38
|
+
"""Zip with another Many."""
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
def find(predicate: Callable[[T], bool]) -> Callable[[Many[T]], Option[T]]:
|
|
42
|
+
"""Find first matching item."""
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
@overload
|
|
46
|
+
def sort(*, reverse: bool = ...) -> Callable[[Many[Any]], Many[Any]]:
|
|
47
|
+
"""Sort items with default key."""
|
|
48
|
+
...
|
|
49
|
+
|
|
50
|
+
@overload
|
|
51
|
+
def sort(key: Callable[[T], Any], reverse: bool = ...) -> Callable[[Many[T]], Many[T]]:
|
|
52
|
+
"""Sort items with custom key."""
|
|
53
|
+
...
|
|
54
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Common functional combinators: identity, const, tap, tee, fmt."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, TypeVar
|
|
4
|
+
|
|
5
|
+
T = TypeVar("T")
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def identity(x: T) -> T:
|
|
9
|
+
"""Return the input unchanged (pass-through).
|
|
10
|
+
|
|
11
|
+
Usage: stream >> identity
|
|
12
|
+
"""
|
|
13
|
+
return x
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def const(value: T) -> Callable[[Any], T]:
|
|
17
|
+
"""Return a function that always returns the given value.
|
|
18
|
+
|
|
19
|
+
Usage: input >> const(10) # returns 10
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def wrapper(_: Any) -> T:
|
|
23
|
+
return value
|
|
24
|
+
|
|
25
|
+
return wrapper
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def tap(func: Callable[[T], Any]) -> Callable[[T], T]:
|
|
29
|
+
"""Execute func(x) for side effect and return x unchanged.
|
|
30
|
+
|
|
31
|
+
Usage: Ok(x) >> tap(print)
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def wrapper(x: T) -> T:
|
|
35
|
+
func(x)
|
|
36
|
+
return x
|
|
37
|
+
|
|
38
|
+
return wrapper
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def tee(func: Callable[[T], Any]) -> Callable[[T], T]:
|
|
42
|
+
"""Execute func(x) and return x. Alias for tap.
|
|
43
|
+
|
|
44
|
+
Usage: Ok(x) >> tee(log_to_db)
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def wrapper(x: T) -> T:
|
|
48
|
+
func(x)
|
|
49
|
+
return x
|
|
50
|
+
|
|
51
|
+
return wrapper
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def fmt(template: str) -> Callable[[Any], str]:
|
|
55
|
+
"""Format a value into a template string.
|
|
56
|
+
|
|
57
|
+
Returns a function that calls template.format(x).
|
|
58
|
+
|
|
59
|
+
Usage: Ok("Alice") >> fmt("Hello, {}!") # Ok("Hello, Alice!")
|
|
60
|
+
compose(_.name, fmt("{}: adult"))
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
def wrapper(x: Any) -> str:
|
|
64
|
+
return template.format(x)
|
|
65
|
+
|
|
66
|
+
return wrapper
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Type stubs for common module."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, TypeVar
|
|
4
|
+
|
|
5
|
+
T = TypeVar("T")
|
|
6
|
+
|
|
7
|
+
def identity(x: T) -> T:
|
|
8
|
+
"""Return input unchanged."""
|
|
9
|
+
...
|
|
10
|
+
|
|
11
|
+
def const(value: T) -> Callable[[Any], T]:
|
|
12
|
+
"""Return function that always returns value."""
|
|
13
|
+
...
|
|
14
|
+
|
|
15
|
+
def tap(func: Callable[[T], Any]) -> Callable[[T], T]:
|
|
16
|
+
"""Execute side-effect and return input."""
|
|
17
|
+
...
|
|
18
|
+
|
|
19
|
+
def tee(func: Callable[[T], Any]) -> Callable[[T], T]:
|
|
20
|
+
"""Alias for tap."""
|
|
21
|
+
...
|
|
22
|
+
|
|
23
|
+
def fmt(template: str) -> Callable[[Any], str]:
|
|
24
|
+
"""Format value into template string."""
|
|
25
|
+
...
|