pional 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.
- pional-0.1.0/PKG-INFO +108 -0
- pional-0.1.0/README.md +98 -0
- pional-0.1.0/pyproject.toml +18 -0
- pional-0.1.0/src/pional/__init__.py +9 -0
- pional-0.1.0/src/pional/adts/__init__.py +8 -0
- pional-0.1.0/src/pional/adts/option.py +173 -0
- pional-0.1.0/src/pional/adts/result.py +251 -0
- pional-0.1.0/src/pional/collections/__init__.py +10 -0
- pional-0.1.0/src/pional/collections/base.py +20 -0
- pional-0.1.0/src/pional/collections/hashmap.py +90 -0
- pional-0.1.0/src/pional/collections/hashset.py +61 -0
- pional-0.1.0/src/pional/collections/vec.py +89 -0
- pional-0.1.0/src/pional/fn/__init__.py +26 -0
- pional-0.1.0/src/pional/fn/compose.py +41 -0
- pional-0.1.0/src/pional/fn/pipe.py +37 -0
- pional-0.1.0/src/pional/iterators/__init__.py +31 -0
- pional-0.1.0/src/pional/iterators/adapters.py +150 -0
- pional-0.1.0/src/pional/iterators/base.py +507 -0
- pional-0.1.0/src/pional/iterators/combinators.py +34 -0
- pional-0.1.0/src/pional/iterators/indexed.py +51 -0
- pional-0.1.0/src/pional/iterators/observers.py +47 -0
- pional-0.1.0/src/pional/iterators/order.py +50 -0
pional-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: pional
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Add Functional Programing to Python
|
|
5
|
+
Author: NovaH00
|
|
6
|
+
Author-email: NovaH00 <trantay2006super@gmail.com>
|
|
7
|
+
Requires-Python: >=3.14
|
|
8
|
+
Project-URL: Repository, https://github.com/NovaH00/python-FP
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# pional
|
|
12
|
+
|
|
13
|
+
Functional programming primitives for Python, with full type safety.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
uv add pional # or pip install pional
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Example
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from pional.adts import Result, Ok, Err
|
|
25
|
+
from pional.collections import Vec
|
|
26
|
+
from pional.iterators import Range
|
|
27
|
+
|
|
28
|
+
def divide(a: int, b: int) -> Result[float, str]:
|
|
29
|
+
if b == 0:
|
|
30
|
+
return Err("cannot divide by zero")
|
|
31
|
+
return Ok(a / b)
|
|
32
|
+
|
|
33
|
+
first = Range(1, 100)
|
|
34
|
+
second = Range(1, 100)
|
|
35
|
+
|
|
36
|
+
result: Vec[float] = (
|
|
37
|
+
first
|
|
38
|
+
.rev()
|
|
39
|
+
.zip(second)
|
|
40
|
+
.filter_map(lambda ab: divide(*ab).ok())
|
|
41
|
+
.collect(Vec)
|
|
42
|
+
)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Modules
|
|
46
|
+
|
|
47
|
+
### `pional.adts` — Algebraic Data Types
|
|
48
|
+
|
|
49
|
+
- **`Result[T, E] = Ok[T] | Err[E]`** — success/failure
|
|
50
|
+
- `unwrap()`, `unwrap_or(default)`, `unwrap_err()` (Err only)
|
|
51
|
+
- `map(f)`, `map_err(f)`, `and_then(f)`
|
|
52
|
+
- `map_or(default, f)`, `map_or_else(default_fn, f)`
|
|
53
|
+
- `combine(other)`, `combine_with(other, f)`
|
|
54
|
+
- `ok()` → `Option[T]`, `err()` → `Option[E]`
|
|
55
|
+
- `is_ok()`, `is_err()`
|
|
56
|
+
|
|
57
|
+
- **`Option[T] = Some[T] | Nothing`** — optional value
|
|
58
|
+
- `unwrap()`, `unwrap_or(default)`
|
|
59
|
+
- `map(f)`, `and_then(f)`
|
|
60
|
+
- `ok_or(err)`, `ok_or_else(err_fn)` → `Result[T, E]`
|
|
61
|
+
- `is_some()`, `is_none()`
|
|
62
|
+
|
|
63
|
+
### `pional.collections` — Collections
|
|
64
|
+
|
|
65
|
+
- **`Vec[T]`** — immutable-style vector (wraps `list`)
|
|
66
|
+
- `into_iter()` → `Iter[T]`
|
|
67
|
+
- `append(item)`, `extend(items)`, `insert(index, item)`
|
|
68
|
+
- `pop()` → `Option[T]`, `get(index)` → `Option[T]`
|
|
69
|
+
- `sort()`, `sort_by(key)`
|
|
70
|
+
|
|
71
|
+
- **`HashMap[K, V]`** — hash map (wraps `dict`, extends `MutableMapping`)
|
|
72
|
+
- `insert(k, v)`, `remove(k)` → `Option[V]`, `contains(k)`
|
|
73
|
+
- `keys()` → `Iter[K]`, `values()` → `Iter[V]`
|
|
74
|
+
- `into_iter()` → `Iter[tuple[K, V]]`
|
|
75
|
+
|
|
76
|
+
- **`HashSet[T]`** — hash set (wraps `set`, extends `MutableSet`)
|
|
77
|
+
- `insert(x)`, `remove(x)`, `contains(x)`
|
|
78
|
+
- `add(x)`, `discard(x)`
|
|
79
|
+
- `into_iter()` → `Iter[T]`
|
|
80
|
+
|
|
81
|
+
### `pional.iterators` — Iterator Combinators
|
|
82
|
+
|
|
83
|
+
- **`Iter[T]`**, **`Range`** — constructors
|
|
84
|
+
|
|
85
|
+
- **Lazy combinators**:
|
|
86
|
+
`map`, `filter`, `filter_map`, `flat_map`, `flatten`, `enumerate`,
|
|
87
|
+
`take`, `skip`, `chain`, `zip`, `inspect`, `peekable`, `rev`,
|
|
88
|
+
`dedup`, `unique`, `cycle`, `chunks(n)`, `windows(n)`
|
|
89
|
+
|
|
90
|
+
- **Terminal methods**:
|
|
91
|
+
`collect(Vec|HashSet|HashMap)`, `count`, `first`, `last`, `nth`,
|
|
92
|
+
`for_each`, `sum`, `product`, `min`, `max`, `fold`, `reduce`,
|
|
93
|
+
`all`, `any`, `find`, `join`,
|
|
94
|
+
`sorted()`, `sorted_by(key)`, `partition(pred)`, `group_by(key_fn)`
|
|
95
|
+
|
|
96
|
+
### `pional.fn` — Function Utilities
|
|
97
|
+
|
|
98
|
+
- **`Pipe`** — `Pipe(x).pipe(fn1).pipe(fn2)`
|
|
99
|
+
- **`Compose`** — `Compose(fn1).compose(fn2)(x)` (right-to-left)
|
|
100
|
+
- **`identity(x)`** — returns `x`
|
|
101
|
+
- **`constant(x)`** — returns a function that always returns `x`
|
|
102
|
+
|
|
103
|
+
## Design
|
|
104
|
+
|
|
105
|
+
- Fully type-annotated with Python 3.14+ generics (PEP 695)
|
|
106
|
+
- ADTs use `match`-compatible `__match_args__` for pattern matching
|
|
107
|
+
- Iterators are lazy where possible
|
|
108
|
+
- No external dependencies
|
pional-0.1.0/README.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# pional
|
|
2
|
+
|
|
3
|
+
Functional programming primitives for Python, with full type safety.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv add pional # or pip install pional
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Example
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from pional.adts import Result, Ok, Err
|
|
15
|
+
from pional.collections import Vec
|
|
16
|
+
from pional.iterators import Range
|
|
17
|
+
|
|
18
|
+
def divide(a: int, b: int) -> Result[float, str]:
|
|
19
|
+
if b == 0:
|
|
20
|
+
return Err("cannot divide by zero")
|
|
21
|
+
return Ok(a / b)
|
|
22
|
+
|
|
23
|
+
first = Range(1, 100)
|
|
24
|
+
second = Range(1, 100)
|
|
25
|
+
|
|
26
|
+
result: Vec[float] = (
|
|
27
|
+
first
|
|
28
|
+
.rev()
|
|
29
|
+
.zip(second)
|
|
30
|
+
.filter_map(lambda ab: divide(*ab).ok())
|
|
31
|
+
.collect(Vec)
|
|
32
|
+
)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Modules
|
|
36
|
+
|
|
37
|
+
### `pional.adts` — Algebraic Data Types
|
|
38
|
+
|
|
39
|
+
- **`Result[T, E] = Ok[T] | Err[E]`** — success/failure
|
|
40
|
+
- `unwrap()`, `unwrap_or(default)`, `unwrap_err()` (Err only)
|
|
41
|
+
- `map(f)`, `map_err(f)`, `and_then(f)`
|
|
42
|
+
- `map_or(default, f)`, `map_or_else(default_fn, f)`
|
|
43
|
+
- `combine(other)`, `combine_with(other, f)`
|
|
44
|
+
- `ok()` → `Option[T]`, `err()` → `Option[E]`
|
|
45
|
+
- `is_ok()`, `is_err()`
|
|
46
|
+
|
|
47
|
+
- **`Option[T] = Some[T] | Nothing`** — optional value
|
|
48
|
+
- `unwrap()`, `unwrap_or(default)`
|
|
49
|
+
- `map(f)`, `and_then(f)`
|
|
50
|
+
- `ok_or(err)`, `ok_or_else(err_fn)` → `Result[T, E]`
|
|
51
|
+
- `is_some()`, `is_none()`
|
|
52
|
+
|
|
53
|
+
### `pional.collections` — Collections
|
|
54
|
+
|
|
55
|
+
- **`Vec[T]`** — immutable-style vector (wraps `list`)
|
|
56
|
+
- `into_iter()` → `Iter[T]`
|
|
57
|
+
- `append(item)`, `extend(items)`, `insert(index, item)`
|
|
58
|
+
- `pop()` → `Option[T]`, `get(index)` → `Option[T]`
|
|
59
|
+
- `sort()`, `sort_by(key)`
|
|
60
|
+
|
|
61
|
+
- **`HashMap[K, V]`** — hash map (wraps `dict`, extends `MutableMapping`)
|
|
62
|
+
- `insert(k, v)`, `remove(k)` → `Option[V]`, `contains(k)`
|
|
63
|
+
- `keys()` → `Iter[K]`, `values()` → `Iter[V]`
|
|
64
|
+
- `into_iter()` → `Iter[tuple[K, V]]`
|
|
65
|
+
|
|
66
|
+
- **`HashSet[T]`** — hash set (wraps `set`, extends `MutableSet`)
|
|
67
|
+
- `insert(x)`, `remove(x)`, `contains(x)`
|
|
68
|
+
- `add(x)`, `discard(x)`
|
|
69
|
+
- `into_iter()` → `Iter[T]`
|
|
70
|
+
|
|
71
|
+
### `pional.iterators` — Iterator Combinators
|
|
72
|
+
|
|
73
|
+
- **`Iter[T]`**, **`Range`** — constructors
|
|
74
|
+
|
|
75
|
+
- **Lazy combinators**:
|
|
76
|
+
`map`, `filter`, `filter_map`, `flat_map`, `flatten`, `enumerate`,
|
|
77
|
+
`take`, `skip`, `chain`, `zip`, `inspect`, `peekable`, `rev`,
|
|
78
|
+
`dedup`, `unique`, `cycle`, `chunks(n)`, `windows(n)`
|
|
79
|
+
|
|
80
|
+
- **Terminal methods**:
|
|
81
|
+
`collect(Vec|HashSet|HashMap)`, `count`, `first`, `last`, `nth`,
|
|
82
|
+
`for_each`, `sum`, `product`, `min`, `max`, `fold`, `reduce`,
|
|
83
|
+
`all`, `any`, `find`, `join`,
|
|
84
|
+
`sorted()`, `sorted_by(key)`, `partition(pred)`, `group_by(key_fn)`
|
|
85
|
+
|
|
86
|
+
### `pional.fn` — Function Utilities
|
|
87
|
+
|
|
88
|
+
- **`Pipe`** — `Pipe(x).pipe(fn1).pipe(fn2)`
|
|
89
|
+
- **`Compose`** — `Compose(fn1).compose(fn2)(x)` (right-to-left)
|
|
90
|
+
- **`identity(x)`** — returns `x`
|
|
91
|
+
- **`constant(x)`** — returns a function that always returns `x`
|
|
92
|
+
|
|
93
|
+
## Design
|
|
94
|
+
|
|
95
|
+
- Fully type-annotated with Python 3.14+ generics (PEP 695)
|
|
96
|
+
- ADTs use `match`-compatible `__match_args__` for pattern matching
|
|
97
|
+
- Iterators are lazy where possible
|
|
98
|
+
- No external dependencies
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pional"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Add Functional Programing to Python"
|
|
5
|
+
authors = [
|
|
6
|
+
{ name = "NovaH00", email = "trantay2006super@gmail.com" }
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.14"
|
|
10
|
+
dependencies = []
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
[project.urls]
|
|
14
|
+
Repository = "https://github.com/NovaH00/python-FP"
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["uv_build>=0.11.7,<0.12.0"]
|
|
18
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Option type — represents an optional value.
|
|
2
|
+
|
|
3
|
+
Provides ``Some[T]`` for present values and ``Nothing`` for absent values,
|
|
4
|
+
with monadic operations like ``map`` and ``and_then``.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import final, Callable, Any, TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from .result import Result
|
|
12
|
+
|
|
13
|
+
type Option[T] = Some[T] | Nothing
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@final
|
|
17
|
+
class Some[T]:
|
|
18
|
+
"""Represents a present value."""
|
|
19
|
+
|
|
20
|
+
__match_args__ = ('value',)
|
|
21
|
+
|
|
22
|
+
def __init__(self, value: T) -> None:
|
|
23
|
+
self.value = value
|
|
24
|
+
|
|
25
|
+
def unwrap(self) -> T:
|
|
26
|
+
"""Return the inner value."""
|
|
27
|
+
return self.value
|
|
28
|
+
|
|
29
|
+
def unwrap_or(self, default: T) -> T:
|
|
30
|
+
"""Return the inner value, ignoring *default*."""
|
|
31
|
+
return self.value
|
|
32
|
+
|
|
33
|
+
def map[U](self, f: Callable[[T], U]) -> Some[U]:
|
|
34
|
+
"""Transform the inner value using *f*.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
f: The function to apply to the wrapped value.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
A new ``Some`` containing the transformed value.
|
|
41
|
+
"""
|
|
42
|
+
return Some(f(self.value))
|
|
43
|
+
|
|
44
|
+
def and_then[U](self, f: Callable[[T], Option[U]]) -> Option[U]:
|
|
45
|
+
"""Apply *f* to the inner value and return the result.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
f: A function that returns an ``Option``.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
The ``Option`` returned by *f*.
|
|
52
|
+
"""
|
|
53
|
+
return f(self.value)
|
|
54
|
+
|
|
55
|
+
def ok_or[E](self, err: E) -> Result[T, E]:
|
|
56
|
+
"""Convert to ``Ok``, ignoring *err*.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
err: The error value (discarded).
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
``Ok(self.value)``.
|
|
63
|
+
"""
|
|
64
|
+
from .result import Ok as ResultOk
|
|
65
|
+
return ResultOk(self.value)
|
|
66
|
+
|
|
67
|
+
def ok_or_else[E](self, err_fn: Callable[[], E]) -> Result[T, E]:
|
|
68
|
+
"""Convert to ``Ok``, ignoring *err_fn*.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
err_fn: A function producing an error (not called).
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
``Ok(self.value)``.
|
|
75
|
+
"""
|
|
76
|
+
from .result import Ok as ResultOk
|
|
77
|
+
return ResultOk(self.value)
|
|
78
|
+
|
|
79
|
+
def is_some(self) -> bool:
|
|
80
|
+
"""True if this is a ``Some`` value."""
|
|
81
|
+
return True
|
|
82
|
+
|
|
83
|
+
def is_none(self) -> bool:
|
|
84
|
+
"""False — this is a ``Some``."""
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
def __repr__(self) -> str:
|
|
88
|
+
return f'Some({self.value!r})'
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@final
|
|
92
|
+
class Nothing:
|
|
93
|
+
"""Represents an absent value (singleton)."""
|
|
94
|
+
|
|
95
|
+
__match_args__ = ()
|
|
96
|
+
_instance: Nothing | None = None
|
|
97
|
+
|
|
98
|
+
def __new__(cls) -> Nothing:
|
|
99
|
+
if cls._instance is None:
|
|
100
|
+
cls._instance = super().__new__(cls)
|
|
101
|
+
return cls._instance
|
|
102
|
+
|
|
103
|
+
def unwrap(self) -> None:
|
|
104
|
+
"""Raise — cannot unwrap ``Nothing``."""
|
|
105
|
+
raise ValueError('Called unwrap on Nothing')
|
|
106
|
+
|
|
107
|
+
def unwrap_or[T](self, default: T) -> T:
|
|
108
|
+
"""Return *default*.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
default: The fallback value.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
*default* unchanged.
|
|
115
|
+
"""
|
|
116
|
+
return default
|
|
117
|
+
|
|
118
|
+
def ok_or[T, E](self, err: E) -> Result[T, E]:
|
|
119
|
+
"""Convert to ``Err`` using *err*.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
err: The error value.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
``Err(err)``.
|
|
126
|
+
"""
|
|
127
|
+
from .result import Err as ResultErr
|
|
128
|
+
return ResultErr(err)
|
|
129
|
+
|
|
130
|
+
def ok_or_else[T, E](self, err_fn: Callable[[], E]) -> Result[T, E]:
|
|
131
|
+
"""Convert to ``Err`` by calling *err_fn*.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
err_fn: A function producing the error value.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
``Err(err_fn())``.
|
|
138
|
+
"""
|
|
139
|
+
from .result import Err as ResultErr
|
|
140
|
+
return ResultErr(err_fn())
|
|
141
|
+
|
|
142
|
+
def map[U](self, f: Callable[[Any], U]) -> Nothing:
|
|
143
|
+
"""No-op — returns ``Nothing`` unchanged.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
f: The function (not called).
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
``self`` (``Nothing``).
|
|
150
|
+
"""
|
|
151
|
+
return self
|
|
152
|
+
|
|
153
|
+
def and_then[U](self, f: Callable[[Any], Option[U]]) -> Nothing:
|
|
154
|
+
"""No-op — returns ``Nothing`` unchanged.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
f: The function (not called).
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
``self`` (``Nothing``).
|
|
161
|
+
"""
|
|
162
|
+
return self
|
|
163
|
+
|
|
164
|
+
def is_some(self) -> bool:
|
|
165
|
+
"""False — this is ``Nothing``."""
|
|
166
|
+
return False
|
|
167
|
+
|
|
168
|
+
def is_none(self) -> bool:
|
|
169
|
+
"""True if this is ``Nothing``."""
|
|
170
|
+
return True
|
|
171
|
+
|
|
172
|
+
def __repr__(self) -> str:
|
|
173
|
+
return 'Nothing'
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""Result type — represents success (``Ok``) or failure (``Err``).
|
|
2
|
+
|
|
3
|
+
Provides monadic operations and combinators for working with fallible
|
|
4
|
+
computations in a type-safe way.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import final, Callable, Any, TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from .option import Option, Nothing
|
|
12
|
+
|
|
13
|
+
type Result[T, E] = Ok[T] | Err[E]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@final
|
|
17
|
+
class Ok[T]:
|
|
18
|
+
"""Represents a successful result containing a value."""
|
|
19
|
+
|
|
20
|
+
__match_args__ = ('value',)
|
|
21
|
+
|
|
22
|
+
def __init__(self, value: T) -> None:
|
|
23
|
+
self.value = value
|
|
24
|
+
|
|
25
|
+
def unwrap(self) -> T:
|
|
26
|
+
"""Return the inner value."""
|
|
27
|
+
return self.value
|
|
28
|
+
|
|
29
|
+
def unwrap_or(self, default: T) -> T:
|
|
30
|
+
"""Return the inner value, ignoring *default*."""
|
|
31
|
+
return self.value
|
|
32
|
+
|
|
33
|
+
def map[U](self, f: Callable[[T], U]) -> Ok[U]:
|
|
34
|
+
"""Transform the inner value using *f*.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
f: The function to apply.
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
``Ok(f(self.value))``.
|
|
41
|
+
"""
|
|
42
|
+
return Ok(f(self.value))
|
|
43
|
+
|
|
44
|
+
def map_err(self, f: Callable[[Any], Any]) -> Ok[T]:
|
|
45
|
+
"""No-op — returns ``self`` unchanged."""
|
|
46
|
+
return self
|
|
47
|
+
|
|
48
|
+
def and_then[U, F](self, f: Callable[[T], Result[U, F]]) -> Result[U, F]:
|
|
49
|
+
"""Apply *f* to the inner value and return the result.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
f: A function returning a ``Result``.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
The ``Result`` returned by *f*.
|
|
56
|
+
"""
|
|
57
|
+
return f(self.value)
|
|
58
|
+
|
|
59
|
+
def ok(self) -> Option[T]:
|
|
60
|
+
"""Convert to ``Some(self.value)``."""
|
|
61
|
+
from .option import Some as OptionSome
|
|
62
|
+
return OptionSome(self.value)
|
|
63
|
+
|
|
64
|
+
def err(self) -> Nothing:
|
|
65
|
+
"""Always returns ``Nothing`` — this is an ``Ok``."""
|
|
66
|
+
from .option import Nothing as OptionNothing
|
|
67
|
+
return OptionNothing()
|
|
68
|
+
|
|
69
|
+
def map_or[U](self, default: U, f: Callable[[T], U]) -> U:
|
|
70
|
+
"""Apply *f* to the inner value, ignoring *default*.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
default: The fallback (ignored).
|
|
74
|
+
f: The function to apply.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
``f(self.value)``.
|
|
78
|
+
"""
|
|
79
|
+
return f(self.value)
|
|
80
|
+
|
|
81
|
+
def map_or_else[U](self, default: Callable[[Any], U], f: Callable[[T], U]) -> U:
|
|
82
|
+
"""Apply *f* to the inner value, ignoring *default*.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
default: The fallback function (not called).
|
|
86
|
+
f: The function to apply.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
``f(self.value)``.
|
|
90
|
+
"""
|
|
91
|
+
return f(self.value)
|
|
92
|
+
|
|
93
|
+
def combine[U, E](self, other: Result[U, E]) -> Result[tuple[T, U], E]:
|
|
94
|
+
"""Pair this ``Ok`` with *other*.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
other: Another ``Result``.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
``Ok((self.value, v))`` if *other* is ``Ok(v)``,
|
|
101
|
+
otherwise propagates the ``Err``.
|
|
102
|
+
"""
|
|
103
|
+
match other:
|
|
104
|
+
case Ok(v):
|
|
105
|
+
return Ok((self.value, v))
|
|
106
|
+
case Err(e):
|
|
107
|
+
return Err(e)
|
|
108
|
+
|
|
109
|
+
def combine_with[U, V, E](self, other: Result[U, E], f: Callable[[T, U], V]) -> Result[V, E]:
|
|
110
|
+
"""Combine this ``Ok`` with *other* using *f*.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
other: Another ``Result``.
|
|
114
|
+
f: The merge function.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
``Ok(f(self.value, v))`` if *other* is ``Ok(v)``,
|
|
118
|
+
otherwise propagates the ``Err``.
|
|
119
|
+
"""
|
|
120
|
+
match other:
|
|
121
|
+
case Ok(v):
|
|
122
|
+
return Ok(f(self.value, v))
|
|
123
|
+
case Err(e):
|
|
124
|
+
return Err(e)
|
|
125
|
+
|
|
126
|
+
def is_ok(self) -> bool:
|
|
127
|
+
"""True if this is an ``Ok`` value."""
|
|
128
|
+
return True
|
|
129
|
+
|
|
130
|
+
def is_err(self) -> bool:
|
|
131
|
+
"""False — this is an ``Ok``."""
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
def __repr__(self) -> str:
|
|
135
|
+
return f'Ok({self.value!r})'
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
@final
|
|
139
|
+
class Err[E]:
|
|
140
|
+
"""Represents a failure result containing an error value."""
|
|
141
|
+
|
|
142
|
+
__match_args__ = ('value',)
|
|
143
|
+
|
|
144
|
+
def __init__(self, value: E) -> None:
|
|
145
|
+
self.value = value
|
|
146
|
+
|
|
147
|
+
def unwrap(self) -> None:
|
|
148
|
+
"""Raise — cannot unwrap an ``Err``."""
|
|
149
|
+
raise ValueError(f'Called unwrap on Err: {self.value}')
|
|
150
|
+
|
|
151
|
+
def unwrap_or[T](self, default: T) -> T:
|
|
152
|
+
"""Return *default*.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
default: The fallback value.
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
*default* unchanged.
|
|
159
|
+
"""
|
|
160
|
+
return default
|
|
161
|
+
|
|
162
|
+
def unwrap_err(self) -> E:
|
|
163
|
+
"""Return the inner error value."""
|
|
164
|
+
return self.value
|
|
165
|
+
|
|
166
|
+
def map[U](self, f: Callable[[Any], U]) -> Err[E]:
|
|
167
|
+
"""No-op — returns ``self`` unchanged."""
|
|
168
|
+
return self
|
|
169
|
+
|
|
170
|
+
def map_err[F](self, f: Callable[[E], F]) -> Err[F]:
|
|
171
|
+
"""Transform the error value using *f*.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
f: The function to apply.
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
``Err(f(self.value))``.
|
|
178
|
+
"""
|
|
179
|
+
return Err(f(self.value))
|
|
180
|
+
|
|
181
|
+
def and_then[U](self, f: Callable[[Any], Result[U, Any]]) -> Err[E]:
|
|
182
|
+
"""No-op — returns ``self`` unchanged."""
|
|
183
|
+
return self
|
|
184
|
+
|
|
185
|
+
def ok(self) -> Nothing:
|
|
186
|
+
"""Always returns ``Nothing`` — this is an ``Err``."""
|
|
187
|
+
from .option import Nothing as OptionNothing
|
|
188
|
+
return OptionNothing()
|
|
189
|
+
|
|
190
|
+
def err(self) -> Option[E]:
|
|
191
|
+
"""Convert to ``Some(self.value)``."""
|
|
192
|
+
from .option import Some as OptionSome
|
|
193
|
+
return OptionSome(self.value)
|
|
194
|
+
|
|
195
|
+
def map_or[T, U](self, default: U, f: Callable[[T], U]) -> U:
|
|
196
|
+
"""Return *default*, ignoring *f*.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
default: The fallback value.
|
|
200
|
+
f: The function (not called).
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
*default* unchanged.
|
|
204
|
+
"""
|
|
205
|
+
return default
|
|
206
|
+
|
|
207
|
+
def map_or_else[U](self, default: Callable[[E], U], f: Callable[[Any], U]) -> U:
|
|
208
|
+
"""Call *default* with the error value.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
default: The fallback function.
|
|
212
|
+
f: The function (not called).
|
|
213
|
+
|
|
214
|
+
Returns:
|
|
215
|
+
``default(self.value)``.
|
|
216
|
+
"""
|
|
217
|
+
return default(self.value)
|
|
218
|
+
|
|
219
|
+
def combine[T, U](self, other: Result[U, E]) -> Result[tuple[T, U], E]:
|
|
220
|
+
"""Propagate this ``Err`` — ignores *other*.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
other: Another ``Result`` (ignored).
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
``self`` (the error).
|
|
227
|
+
"""
|
|
228
|
+
return self # type: ignore
|
|
229
|
+
|
|
230
|
+
def combine_with[T, U, V](self, other: Result[U, E], f: Callable[[T, U], V]) -> Result[V, E]:
|
|
231
|
+
"""Propagate this ``Err`` — ignores *other* and *f*.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
other: Another ``Result`` (ignored).
|
|
235
|
+
f: The merge function (not called).
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
``self`` (the error).
|
|
239
|
+
"""
|
|
240
|
+
return self # type: ignore
|
|
241
|
+
|
|
242
|
+
def is_ok(self) -> bool:
|
|
243
|
+
"""False — this is an ``Err``."""
|
|
244
|
+
return False
|
|
245
|
+
|
|
246
|
+
def is_err(self) -> bool:
|
|
247
|
+
"""True if this is an ``Err`` value."""
|
|
248
|
+
return True
|
|
249
|
+
|
|
250
|
+
def __repr__(self) -> str:
|
|
251
|
+
return f'Err({self.value!r})'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Base abstraction for collection types."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import TYPE_CHECKING, Self
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from pional.iterators import IteratorExt, Iter
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CollectionExt[T](ABC):
|
|
11
|
+
"""Abstract base for collections that can be constructed from iterators."""
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def into_iter(self) -> Iter[T]:
|
|
15
|
+
"""Consume and convert into an ``Iter``."""
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def from_iter(cls, iterator: IteratorExt[T]) -> Self:
|
|
20
|
+
"""Construct an instance from an ``IteratorExt``."""
|