funstruct 1.0.1__tar.gz → 1.0.2__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.
- {funstruct-1.0.1 → funstruct-1.0.2}/PKG-INFO +1 -1
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/applicative/validated.py +13 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/monad/result.py +46 -17
- {funstruct-1.0.1 → funstruct-1.0.2}/pyproject.toml +1 -1
- {funstruct-1.0.1 → funstruct-1.0.2}/uv.lock +1 -1
- {funstruct-1.0.1 → funstruct-1.0.2}/.claude/settings.local.json +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/.github/workflows/ci.yml +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/.github/workflows/docs.yml +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/.github/workflows/publish.yml +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/.gitignore +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/.pre-commit-config.yaml +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/CONTRIBUTING.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/LICENSE +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/README.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/benchmarks/test_bench_collections.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/applicative/index.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/applicative/validated.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/collections/cons.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/collections/frozendict.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/collections/index.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/collections/tree.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/functor/.gitkeep +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/functor/index.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/index.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/monad/either.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/monad/future.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/monad/index.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/monad/option.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/monad/reader.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/monad/result.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/monad/state.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/monad/writer.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/monadtransformer/either_t.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/monadtransformer/index.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/monadtransformer/option_t.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/monadtransformer/reader_t.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/monadtransformer/state_t.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/monadtransformer/writer_t.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/typeclass/applicative.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/typeclass/index.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/typeclass/monad.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/typeclass/monadtransformer.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/typeclass/monoid.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/typeclass/semigroup.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/docs/util/tailrec.md +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/__init__.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/applicative/__init__.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/collections/__init__.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/collections/cons.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/collections/frozendict.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/collections/tree.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/functor/__init__.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/monad/__init__.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/monad/either.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/monad/future.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/monad/option.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/monad/reader.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/monad/state.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/monad/writer.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/monadtransformer/__init__.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/monadtransformer/either_t.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/monadtransformer/option_t.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/monadtransformer/reader_t.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/monadtransformer/state_t.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/monadtransformer/writer_t.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/monoid/__init__.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/py.typed +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/semigroup/__init__.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/typeclasses/__init__.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/typeclasses/_applicative.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/typeclasses/_functor.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/typeclasses/_monad.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/typeclasses/_monad_transformer.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/typeclasses/_monoid.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/typeclasses/_semigroup.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/util/__init__.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/funstruct/util/tailrec.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/justfile +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/mise.toml +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/mkdocs.yml +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/noxfile.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/tests/__init__.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/tests/laws.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/tests/test_cons.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/tests/test_either.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/tests/test_either_t.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/tests/test_frozendict.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/tests/test_future.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/tests/test_law_violations.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/tests/test_option.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/tests/test_option_t.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/tests/test_reader.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/tests/test_reader_t.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/tests/test_result.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/tests/test_state.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/tests/test_state_t.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/tests/test_tailrec.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/tests/test_tree.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/tests/test_validated.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/tests/test_writer.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/tests/test_writer_t.py +0 -0
- {funstruct-1.0.1 → funstruct-1.0.2}/uv.toml +0 -0
|
@@ -17,6 +17,10 @@ from __future__ import annotations
|
|
|
17
17
|
from abc import abstractmethod
|
|
18
18
|
from collections.abc import Callable
|
|
19
19
|
from dataclasses import dataclass
|
|
20
|
+
from typing import TYPE_CHECKING
|
|
21
|
+
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
from funstruct.monad.result import Result
|
|
20
24
|
from typing import Generic, TypeVar
|
|
21
25
|
|
|
22
26
|
from funstruct.collections.cons import Cons
|
|
@@ -33,6 +37,9 @@ class Validated(Applicative):
|
|
|
33
37
|
@abstractmethod
|
|
34
38
|
def ap(self, other) -> Validated: ...
|
|
35
39
|
|
|
40
|
+
def __add__(self, other) -> Validated:
|
|
41
|
+
return self.ap(other)
|
|
42
|
+
|
|
36
43
|
@property
|
|
37
44
|
@abstractmethod
|
|
38
45
|
def is_valid(self) -> bool: ...
|
|
@@ -42,6 +49,12 @@ class Validated(Applicative):
|
|
|
42
49
|
"""Eliminate the Validated — apply on_invalid or on_valid."""
|
|
43
50
|
...
|
|
44
51
|
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def to_result(self) -> Result: ...
|
|
54
|
+
|
|
55
|
+
@abstractmethod
|
|
56
|
+
def to_result_or(self, exc_cls, combine=None) -> Result: ...
|
|
57
|
+
|
|
45
58
|
@classmethod
|
|
46
59
|
def pure(cls, value) -> Validated:
|
|
47
60
|
"""Lift a value into Valid."""
|
|
@@ -35,14 +35,16 @@ Examples:
|
|
|
35
35
|
from __future__ import annotations
|
|
36
36
|
|
|
37
37
|
import inspect
|
|
38
|
-
from
|
|
38
|
+
from abc import abstractmethod
|
|
39
|
+
from collections.abc import Awaitable, Callable, Coroutine
|
|
39
40
|
from dataclasses import dataclass
|
|
40
41
|
from functools import wraps
|
|
41
|
-
from typing import Any, Generic, ParamSpec, TypeVar
|
|
42
|
+
from typing import Any, Generic, ParamSpec, TypeVar, overload
|
|
42
43
|
|
|
43
44
|
from funstruct.monad.either import Either, Left, Right
|
|
44
45
|
|
|
45
46
|
_A = TypeVar("_A")
|
|
47
|
+
_B = TypeVar("_B")
|
|
46
48
|
|
|
47
49
|
|
|
48
50
|
class Result(Either[Exception, _A], Generic[_A]):
|
|
@@ -52,19 +54,32 @@ class Result(Either[Exception, _A], Generic[_A]):
|
|
|
52
54
|
Same as Either[Exception, A] but with 1 param for cleaner annotations.
|
|
53
55
|
"""
|
|
54
56
|
|
|
55
|
-
|
|
57
|
+
@abstractmethod
|
|
58
|
+
def map(self, f: Callable[[_A], _B]) -> Result[_B]: ...
|
|
59
|
+
@abstractmethod
|
|
60
|
+
def bind(self, f: Callable[[_A], Result[_B]]) -> Result[_B]: ...
|
|
61
|
+
@abstractmethod
|
|
62
|
+
def alt(self, f: Callable[[Exception], Exception]) -> Result[_A]: ...
|
|
63
|
+
@abstractmethod
|
|
64
|
+
def or_else(self, f: Callable[[Exception], Result[_A]]) -> Result[_A]: ...
|
|
56
65
|
|
|
57
66
|
|
|
58
67
|
@dataclass(frozen=True, eq=False)
|
|
59
68
|
class Ok(Right):
|
|
60
69
|
"""Success case of Result."""
|
|
61
70
|
|
|
62
|
-
def map(self, f: Callable) ->
|
|
71
|
+
def map(self, f: Callable[[_A], _B]) -> Result[_B]:
|
|
63
72
|
return Ok(f(self.value))
|
|
64
73
|
|
|
65
|
-
def bind(self, f: Callable) ->
|
|
74
|
+
def bind(self, f: Callable[[_A], Result[_B]]) -> Result[_B]:
|
|
66
75
|
return f(self.value)
|
|
67
76
|
|
|
77
|
+
def alt(self, f: Callable[[Exception], Exception]) -> Result[_A]:
|
|
78
|
+
return self
|
|
79
|
+
|
|
80
|
+
def or_else(self, f: Callable[[Exception], Result[_A]]) -> Result[_A]:
|
|
81
|
+
return self
|
|
82
|
+
|
|
68
83
|
def __repr__(self) -> str:
|
|
69
84
|
return f"Ok({repr(self.value)})"
|
|
70
85
|
|
|
@@ -73,10 +88,16 @@ class Ok(Right):
|
|
|
73
88
|
class Err(Left):
|
|
74
89
|
"""Error case of Result."""
|
|
75
90
|
|
|
76
|
-
def
|
|
91
|
+
def map(self, f: Callable[[_A], _B]) -> Result[_B]:
|
|
92
|
+
return self
|
|
93
|
+
|
|
94
|
+
def bind(self, f: Callable[[_A], Result[_B]]) -> Result[_B]:
|
|
95
|
+
return self
|
|
96
|
+
|
|
97
|
+
def alt(self, f: Callable[[Exception], Exception]) -> Result[_A]:
|
|
77
98
|
return Err(f(self.error))
|
|
78
99
|
|
|
79
|
-
def or_else(self, f: Callable) ->
|
|
100
|
+
def or_else(self, f: Callable[[Exception], Result[_A]]) -> Result[_A]:
|
|
80
101
|
return f(self.error)
|
|
81
102
|
|
|
82
103
|
def __repr__(self) -> str:
|
|
@@ -96,7 +117,7 @@ class AsyncResult(Generic[_A]):
|
|
|
96
117
|
|
|
97
118
|
__slots__ = ("_coro",)
|
|
98
119
|
|
|
99
|
-
def __init__(self, coro: Awaitable[
|
|
120
|
+
def __init__(self, coro: Awaitable[Result[_A]]) -> None:
|
|
100
121
|
self._coro = coro
|
|
101
122
|
|
|
102
123
|
def __del__(self):
|
|
@@ -106,10 +127,10 @@ class AsyncResult(Generic[_A]):
|
|
|
106
127
|
def __await__(self):
|
|
107
128
|
return self._awaitable().__await__()
|
|
108
129
|
|
|
109
|
-
async def _awaitable(self) ->
|
|
130
|
+
async def _awaitable(self) -> Result[_A]:
|
|
110
131
|
return await self._coro
|
|
111
132
|
|
|
112
|
-
def map(self, f: Callable[[_A],
|
|
133
|
+
def map(self, f: Callable[[_A], _B]) -> AsyncResult[_B]:
|
|
113
134
|
"""Transform the success value without executing."""
|
|
114
135
|
|
|
115
136
|
async def _inner():
|
|
@@ -118,11 +139,11 @@ class AsyncResult(Generic[_A]):
|
|
|
118
139
|
|
|
119
140
|
return AsyncResult(_inner())
|
|
120
141
|
|
|
121
|
-
def bind(self, f: Callable) -> AsyncResult:
|
|
142
|
+
def bind(self, f: Callable[[_A], Any]) -> AsyncResult:
|
|
122
143
|
"""Chain: f receives value. Short-circuits on Err.
|
|
123
144
|
|
|
124
145
|
Handles all return types from f:
|
|
125
|
-
- AsyncResult[B] → awaited, produces
|
|
146
|
+
- AsyncResult[B] → awaited, produces Result
|
|
126
147
|
- Either[E, B] / Result[B] → used directly
|
|
127
148
|
- Awaitable[B] → awaited, plain value wrapped in Ok
|
|
128
149
|
- Plain B → wrapped in Ok
|
|
@@ -143,7 +164,7 @@ class AsyncResult(Generic[_A]):
|
|
|
143
164
|
|
|
144
165
|
return AsyncResult(_inner())
|
|
145
166
|
|
|
146
|
-
def alt(self, f: Callable) -> AsyncResult:
|
|
167
|
+
def alt(self, f: Callable[[Exception], Exception]) -> AsyncResult[_A]:
|
|
147
168
|
"""Transform the error without recovering."""
|
|
148
169
|
|
|
149
170
|
async def _inner():
|
|
@@ -156,11 +177,11 @@ class AsyncResult(Generic[_A]):
|
|
|
156
177
|
|
|
157
178
|
return AsyncResult(_inner())
|
|
158
179
|
|
|
159
|
-
def or_else(self, f: Callable) -> AsyncResult:
|
|
180
|
+
def or_else(self, f: Callable[[Exception], Any]) -> AsyncResult:
|
|
160
181
|
"""Recover from error: f receives error. Short-circuits on success.
|
|
161
182
|
|
|
162
183
|
Handles all return types from f:
|
|
163
|
-
- AsyncResult[A] → awaited, produces
|
|
184
|
+
- AsyncResult[A] → awaited, produces Result
|
|
164
185
|
- Either[E, A] / Result[A] → used directly
|
|
165
186
|
- Awaitable[A] → awaited, plain value wrapped in Ok
|
|
166
187
|
- Plain A → wrapped in Ok
|
|
@@ -181,11 +202,11 @@ class AsyncResult(Generic[_A]):
|
|
|
181
202
|
|
|
182
203
|
return AsyncResult(_inner())
|
|
183
204
|
|
|
184
|
-
def then(self, next_result: AsyncResult) -> AsyncResult:
|
|
205
|
+
def then(self, next_result: AsyncResult[_B]) -> AsyncResult[_B]:
|
|
185
206
|
"""Sequence: run self, discard value, run next."""
|
|
186
207
|
return self.bind(lambda _: next_result)
|
|
187
208
|
|
|
188
|
-
def ap(self, other: AsyncResult) -> AsyncResult:
|
|
209
|
+
def ap(self, other: AsyncResult[_B]) -> AsyncResult[tuple[_A, _B]]:
|
|
189
210
|
"""Applicative: run both, tuple the values."""
|
|
190
211
|
return self.bind(lambda a: other.map(lambda b: (a, b)))
|
|
191
212
|
|
|
@@ -239,6 +260,14 @@ def Try(
|
|
|
239
260
|
return wrapper
|
|
240
261
|
|
|
241
262
|
|
|
263
|
+
@overload
|
|
264
|
+
def TryAsync(
|
|
265
|
+
f: Callable[_P, Coroutine[Any, Any, _A]],
|
|
266
|
+
) -> Callable[_P, AsyncResult[_A]]: ...
|
|
267
|
+
@overload
|
|
268
|
+
def TryAsync(
|
|
269
|
+
f: Callable[_P, _A],
|
|
270
|
+
) -> Callable[_P, AsyncResult[_A]]: ...
|
|
242
271
|
def TryAsync(
|
|
243
272
|
f: Callable[_P, _A],
|
|
244
273
|
) -> Callable[_P, AsyncResult[_A]]:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|