pional 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.
pional/__init__.py ADDED
@@ -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
+ ]
pional/adts/option.py ADDED
@@ -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'
pional/adts/result.py ADDED
@@ -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``."""
@@ -0,0 +1,90 @@
1
+ """HashMap — a hash map wrapping ``dict``."""
2
+ from __future__ import annotations
3
+ from collections.abc import (
4
+ Iterable,
5
+ MutableMapping,
6
+ Mapping,
7
+ )
8
+
9
+ from typing import TYPE_CHECKING
10
+ if TYPE_CHECKING:
11
+ from pional.iterators import Iter, IteratorExt
12
+ from pional.adts import Option, Some, Nothing
13
+
14
+ from .base import CollectionExt
15
+
16
+
17
+ class HashMap[K, V](MutableMapping[K, V], CollectionExt[tuple[K, V]]):
18
+ """A mutable hash map backed by ``dict``."""
19
+
20
+ def __init__(
21
+ self,
22
+ items: Mapping[K, V] | Iterable[tuple[K, V]] = (),
23
+ ):
24
+ self._data: dict[K, V] = dict(items)
25
+
26
+ def __str__(self) -> str:
27
+ return f"HashMap({self._data})"
28
+
29
+ def __repr__(self) -> str:
30
+ return f"HashMap({self._data!r})"
31
+
32
+ def __getitem__(self, key: K) -> V:
33
+ return self._data[key]
34
+
35
+ def __setitem__(self, key: K, value: V) -> None:
36
+ self._data[key] = value
37
+
38
+ def __delitem__(self, key: K) -> None:
39
+ del self._data[key]
40
+
41
+ def __iter__(self) -> Iter[tuple[K, V]]:
42
+ return self.into_iter()
43
+
44
+ def __len__(self) -> int:
45
+ return len(self._data)
46
+
47
+ def keys(self) -> Iter[K]:
48
+ """Return an iterator over keys."""
49
+ return Iter(self._data.keys())
50
+
51
+ def values(self) -> Iter[V]:
52
+ """Return an iterator over values."""
53
+ return Iter(self._data.values())
54
+
55
+ def into_iter(self) -> Iter[tuple[K, V]]:
56
+ """Consume and convert into an ``Iter`` of ``(key, value)`` pairs."""
57
+ from pional.iterators import Iter
58
+ return Iter(self._data.items())
59
+
60
+ def insert(self, key: K, value: V) -> None:
61
+ """Insert a key-value pair.
62
+
63
+ Args:
64
+ key: The key.
65
+ value: The associated value.
66
+ """
67
+ self._data[key] = value
68
+
69
+ def remove(self, key: K) -> Option[V]:
70
+ """Remove the value associated with *key*.
71
+
72
+ Args:
73
+ key: The key to remove.
74
+
75
+ Returns:
76
+ ``Some(V)`` if the key existed, ``Nothing`` otherwise.
77
+ """
78
+ try:
79
+ return Some(self._data.pop(key))
80
+ except KeyError:
81
+ return Nothing()
82
+
83
+ def contains(self, key: K) -> bool:
84
+ """True if *key* exists in the map."""
85
+ return key in self._data
86
+
87
+ @classmethod
88
+ def from_iter(cls, iterator: IteratorExt[tuple[K, V]]) -> HashMap[K, V]:
89
+ """Construct a ``HashMap`` from an ``IteratorExt`` of pairs."""
90
+ return HashMap(iterator)
@@ -0,0 +1,61 @@
1
+ """HashSet — a hash set wrapping ``set``."""
2
+ from __future__ import annotations
3
+ from collections.abc import (
4
+ Iterable,
5
+ MutableSet,
6
+ )
7
+
8
+ from typing import TYPE_CHECKING
9
+
10
+ if TYPE_CHECKING:
11
+ from pional.iterators import Iter, IteratorExt
12
+
13
+ from .base import CollectionExt
14
+
15
+
16
+ class HashSet[T](MutableSet[T], CollectionExt[T]):
17
+ """A mutable hash set backed by ``set``."""
18
+
19
+ def __init__(self, items: Iterable[T] = ()):
20
+ self._set: set[T] = set(items)
21
+
22
+ def __str__(self) -> str:
23
+ return f"HashSet({self._set})"
24
+
25
+ def __repr__(self) -> str:
26
+ return f"HashMap({self._set!r})"
27
+
28
+ def __contains__(self, item: T) -> bool:
29
+ return item in self._set
30
+
31
+ def __iter__(self) -> Iter[T]:
32
+ return self.into_iter()
33
+
34
+ def __len__(self) -> int:
35
+ return len(self._set)
36
+
37
+ def add(self, value: T) -> None:
38
+ """Add *value* to the set."""
39
+ self._set.add(value)
40
+
41
+ def insert(self, value: T) -> None:
42
+ """Alias for ``add``."""
43
+ self._set.add(value)
44
+
45
+ def contains(self, value: T) -> bool:
46
+ """True if *value* is in the set."""
47
+ return value in self._set
48
+
49
+ def discard(self, value: T) -> None:
50
+ """Remove *value* if present, otherwise no-op."""
51
+ self._set.discard(value)
52
+
53
+ def into_iter(self) -> Iter[T]:
54
+ """Consume and convert into an ``Iter``."""
55
+ from pional.iterators import Iter
56
+ return Iter(self._set)
57
+
58
+ @classmethod
59
+ def from_iter(cls, iterator: IteratorExt[T]) -> HashSet[T]:
60
+ """Construct a ``HashSet`` from an ``IteratorExt``."""
61
+ return HashSet(iterator)