stolas 0.1.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.
- stolas/__init__.py +0 -0
- stolas/logic/__init__.py +56 -0
- stolas/logic/access.py +44 -0
- stolas/logic/access.pyi +17 -0
- stolas/logic/collection.py +137 -0
- stolas/logic/collection.pyi +54 -0
- stolas/logic/common.py +66 -0
- stolas/logic/common.pyi +25 -0
- stolas/logic/flow.py +37 -0
- stolas/logic/flow.pyi +16 -0
- stolas/logic/placeholder.py +296 -0
- stolas/logic/placeholder.pyi +81 -0
- stolas/logic/predicates.py +107 -0
- stolas/logic/predicates.pyi +19 -0
- stolas/logic/utils.py +66 -0
- stolas/logic/utils.pyi +27 -0
- stolas/mypy_plugin.py +62 -0
- stolas/operand/__init__.py +30 -0
- stolas/operand/arity.py +213 -0
- stolas/operand/arity.pyi +85 -0
- stolas/operand/cases.py +146 -0
- stolas/operand/cases.pyi +19 -0
- stolas/operand/concurrent.py +31 -0
- stolas/operand/concurrent.pyi +14 -0
- stolas/operand/ops.py +37 -0
- stolas/operand/ops.pyi +21 -0
- stolas/operand/safe.py +131 -0
- stolas/operand/safe.pyi +24 -0
- stolas/py.typed +0 -0
- stolas/struct/__init__.py +6 -0
- stolas/struct/struct.py +149 -0
- stolas/struct/struct.pyi +15 -0
- stolas/struct/trait.py +258 -0
- stolas/struct/trait.pyi +71 -0
- stolas/types/__init__.py +21 -0
- stolas/types/effect.py +79 -0
- stolas/types/effect.pyi +25 -0
- stolas/types/many.py +126 -0
- stolas/types/many.pyi +31 -0
- stolas/types/option.py +127 -0
- stolas/types/option.pyi +45 -0
- stolas/types/result.py +146 -0
- stolas/types/result.pyi +50 -0
- stolas/types/validated.py +122 -0
- stolas/types/validated.pyi +43 -0
- stolas-0.1.0.dist-info/LICENSE.md +21 -0
- stolas-0.1.0.dist-info/METADATA +120 -0
- stolas-0.1.0.dist-info/RECORD +50 -0
- stolas-0.1.0.dist-info/WHEEL +5 -0
- stolas-0.1.0.dist-info/top_level.txt +1 -0
stolas/__init__.py
ADDED
|
File without changes
|
stolas/logic/__init__.py
ADDED
|
@@ -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
|
+
]
|
stolas/logic/access.py
ADDED
|
@@ -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
|
stolas/logic/access.pyi
ADDED
|
@@ -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
|
+
|
stolas/logic/common.py
ADDED
|
@@ -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
|
stolas/logic/common.pyi
ADDED
|
@@ -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
|
+
...
|
stolas/logic/flow.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Flow control helpers: check, strict."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, TypeVar
|
|
4
|
+
|
|
5
|
+
from stolas.types.result import Error, Ok
|
|
6
|
+
|
|
7
|
+
T = TypeVar("T")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def check(
|
|
11
|
+
predicate: Callable[[T], bool], error_msg: str
|
|
12
|
+
) -> Callable[[T], Ok[T] | Error[str]]:
|
|
13
|
+
"""Validate value with predicate, return Ok or Error.
|
|
14
|
+
|
|
15
|
+
Usage: Ok(val) >> check(_ > 0, "must be positive")
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def wrapper(x: T) -> Ok[T] | Error[str]:
|
|
19
|
+
if predicate(x):
|
|
20
|
+
return Ok(x)
|
|
21
|
+
return Error(error_msg)
|
|
22
|
+
|
|
23
|
+
return wrapper
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def strict(type_: type[T]) -> Callable[[Any], Ok[T] | Error[TypeError]]:
|
|
27
|
+
"""Validate that value is instance of type, return Ok or Error.
|
|
28
|
+
|
|
29
|
+
Usage: Ok(val) >> strict(int)
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def wrapper(x: Any) -> Ok[T] | Error[TypeError]:
|
|
33
|
+
if isinstance(x, type_):
|
|
34
|
+
return Ok(x)
|
|
35
|
+
return Error(TypeError(f"Expected {type_.__name__}, got {type(x).__name__}"))
|
|
36
|
+
|
|
37
|
+
return wrapper
|
stolas/logic/flow.pyi
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Type stubs for flow module."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, TypeVar
|
|
4
|
+
from stolas.types.result import Ok, Error
|
|
5
|
+
|
|
6
|
+
T = TypeVar("T")
|
|
7
|
+
|
|
8
|
+
def check(
|
|
9
|
+
predicate: Callable[[T], bool], error_msg: str
|
|
10
|
+
) -> Callable[[T], Ok[T] | Error[str]]:
|
|
11
|
+
"""Validate with predicate, return Ok or Error."""
|
|
12
|
+
...
|
|
13
|
+
|
|
14
|
+
def strict(type_: type[T]) -> Callable[[Any], Ok[T] | Error[TypeError]]:
|
|
15
|
+
"""Type validation returning Ok or Error."""
|
|
16
|
+
...
|