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 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,9 @@
1
+ """pional — Functional programming primitives for Python."""
2
+ from . import iterators, collections, adts, fn
3
+
4
+ __all__ = [
5
+ "iterators",
6
+ "collections",
7
+ "adts",
8
+ "fn"
9
+ ]
@@ -0,0 +1,8 @@
1
+ """Algebraic data types — Option and Result."""
2
+ from .option import Option, Some, Nothing
3
+ from .result import Result, Ok, Err
4
+
5
+ __all__ = [
6
+ "Option", "Some", "Nothing",
7
+ "Result", "Ok", "Err"
8
+ ]
@@ -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,10 @@
1
+ """Collection types — Vec, HashMap, HashSet."""
2
+ from .vec import Vec
3
+ from .hashmap import HashMap
4
+ from .hashset import HashSet
5
+
6
+ __all__ = [
7
+ "Vec",
8
+ "HashMap",
9
+ "HashSet",
10
+ ]
@@ -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``."""