punctional 0.1.0__py3-none-any.whl → 0.1.1__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.
punctional/__init__.py CHANGED
@@ -13,7 +13,7 @@ from .string import ToUpper, ToLower, Contains
13
13
  from .list_filters import Map, FilterList, Reduce
14
14
  from .monads import Option, Some, Nothing, Result, Ok, Error, some, try_result
15
15
 
16
- __version__ = "0.1.0"
16
+ __version__ = "0.1.1"
17
17
 
18
18
  __all__ = [
19
19
  # Core
@@ -0,0 +1,426 @@
1
+ Metadata-Version: 2.4
2
+ Name: punctional
3
+ Version: 0.1.1
4
+ Summary: A functional programming framework for Python — composable filters, method chaining, and expressive data pipelines with Option and Result monads.
5
+ Project-URL: Homepage, https://github.com/peghaz/punctional
6
+ Project-URL: Repository, https://github.com/peghaz/punctional
7
+ Project-URL: Documentation, https://github.com/peghaz/punctional#readme
8
+ Project-URL: Issues, https://github.com/peghaz/punctional/issues
9
+ Author-email: Mehdi Peghaz <peghaz@example.com>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: composition,data-pipeline,filters,functional-programming,method-chaining,monads,option,pipe-operator,result
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.12
23
+ Description-Content-Type: text/markdown
24
+
25
+ # Punctional
26
+
27
+ > Functional programming for Python — monads, composable filters, and expressive data pipelines.
28
+
29
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
30
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
31
+
32
+ ## What is Punctional?
33
+
34
+ **Punctional** brings robust functional programming patterns to Python. It provides:
35
+
36
+ - **Monads** for safe error handling and null-safety (`Option`, `Result`)
37
+ - **Composable filters** that chain with the pipe (`|`) operator
38
+ - **Functional wrappers** for native types enabling method chaining
39
+
40
+ No dependencies. Pure Python. Type-safe.
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ pip install punctional
46
+ ```
47
+
48
+ Or from source:
49
+
50
+ ```bash
51
+ git clone https://github.com/peghaz/punctional.git
52
+ cd punctional
53
+ pip install -e .
54
+ ```
55
+
56
+ ---
57
+
58
+ ## Core Features
59
+
60
+ ### 1. Option Monad — Safe Null Handling
61
+
62
+ The `Option` type represents a value that may or may not exist. Use `Some` to wrap a value and `Nothing` to represent absence — eliminating `None` checks and `AttributeError` exceptions.
63
+
64
+ ```python
65
+ from punctional import Some, Nothing, some
66
+
67
+ # Wrap existing values
68
+ user_name = Some("Alice")
69
+ print(user_name.map(str.upper)) # Some('ALICE')
70
+ print(user_name.get_or_else("Unknown")) # 'Alice'
71
+
72
+ # Handle missing values safely
73
+ missing = Nothing()
74
+ print(missing.map(str.upper)) # Nothing
75
+ print(missing.get_or_else("Unknown")) # 'Unknown'
76
+
77
+ # Auto-convert from potentially None values
78
+ def find_user(user_id):
79
+ return {"alice": "Alice"}.get(user_id)
80
+
81
+ result = some(find_user("bob")) # Nothing (because get returns None)
82
+ result = some(find_user("alice")) # Some('Alice')
83
+ ```
84
+
85
+ **Option Methods:**
86
+
87
+ | Method | Description |
88
+ |--------|-------------|
89
+ | `map(f)` | Transform the value if present |
90
+ | `flat_map(f)` / `bind(f)` | Chain operations that return `Option` |
91
+ | `filter(predicate)` | Return `Nothing` if predicate fails |
92
+ | `get_or_else(default)` | Get value or return default |
93
+ | `get_or_none()` | Get value or `None` |
94
+ | `or_else(alternative)` | Return alternative `Option` if `Nothing` |
95
+ | `is_some()` / `is_nothing()` | Check presence |
96
+
97
+ **Chaining with Option:**
98
+
99
+ ```python
100
+ from punctional import Some, Nothing
101
+
102
+ def parse_int(s: str) -> Option[int]:
103
+ try:
104
+ return Some(int(s))
105
+ except ValueError:
106
+ return Nothing()
107
+
108
+ def double(x: int) -> Option[int]:
109
+ return Some(x * 2)
110
+
111
+ # Chain operations safely
112
+ result = Some("42").flat_map(parse_int).flat_map(double) # Some(84)
113
+ result = Some("abc").flat_map(parse_int).flat_map(double) # Nothing
114
+ ```
115
+
116
+ ---
117
+
118
+ ### 2. Result Monad — Explicit Error Handling
119
+
120
+ The `Result` type represents either success (`Ok`) or failure (`Error`). Unlike exceptions, errors are explicit in the type signature and must be handled.
121
+
122
+ ```python
123
+ from punctional import Ok, Error, try_result
124
+
125
+ # Explicit success and failure
126
+ success = Ok(42)
127
+ failure = Error("Something went wrong")
128
+
129
+ print(success.map(lambda x: x * 2)) # Ok(84)
130
+ print(failure.map(lambda x: x * 2)) # Error('Something went wrong')
131
+
132
+ print(success.get_or_else(0)) # 42
133
+ print(failure.get_or_else(0)) # 0
134
+ ```
135
+
136
+ **Wrapping exceptions with `try_result`:**
137
+
138
+ ```python
139
+ from punctional import try_result
140
+
141
+ def divide(a, b):
142
+ return a / b
143
+
144
+ result = try_result(lambda: divide(10, 2)) # Ok(5.0)
145
+ result = try_result(lambda: divide(10, 0)) # Error(ZeroDivisionError(...))
146
+
147
+ # With custom error handler
148
+ result = try_result(
149
+ lambda: divide(10, 0),
150
+ error_handler=lambda e: f"Division failed: {e}"
151
+ ) # Error('Division failed: division by zero')
152
+ ```
153
+
154
+ **Result Methods:**
155
+
156
+ | Method | Description |
157
+ |--------|-------------|
158
+ | `map(f)` | Transform success value |
159
+ | `map_error(f)` | Transform error value |
160
+ | `flat_map(f)` / `bind(f)` | Chain operations returning `Result` |
161
+ | `get_or_else(default)` | Get value or default |
162
+ | `is_ok()` / `is_error()` | Check success/failure |
163
+ | `to_option()` | Convert to `Option` (discards error info) |
164
+
165
+ **Railway-oriented programming:**
166
+
167
+ ```python
168
+ from punctional import Result, Ok, Error
169
+
170
+ def validate_age(age: int) -> Result[int, str]:
171
+ if age < 0:
172
+ return Error("Age cannot be negative")
173
+ if age > 150:
174
+ return Error("Age seems unrealistic")
175
+ return Ok(age)
176
+
177
+ def validate_name(name: str) -> Result[str, str]:
178
+ if not name.strip():
179
+ return Error("Name cannot be empty")
180
+ return Ok(name.strip())
181
+
182
+ # Chain validations
183
+ age_result = validate_age(25).map(lambda a: f"Age: {a}") # Ok('Age: 25')
184
+ age_result = validate_age(-5).map(lambda a: f"Age: {a}") # Error('Age cannot be negative')
185
+ ```
186
+
187
+ ---
188
+
189
+ ### 3. Pipe Operator & Filters
190
+
191
+ Chain transformations using the `|` operator for readable, left-to-right data flow.
192
+
193
+ ```python
194
+ from punctional import fint, fstr, ffloat, Add, Mult, Sub, Div, ToUpper
195
+
196
+ # Arithmetic chaining
197
+ result = fint(10) | Add(5) | Mult(2) | Sub(3) # (10 + 5) * 2 - 3 = 27
198
+
199
+ # String transformations
200
+ text = fstr("hello") | ToUpper() | Add(" WORLD!") # 'HELLO WORLD!'
201
+
202
+ # Float operations
203
+ value = ffloat(10.0) | Div(4) | Mult(2) # 5.0
204
+ ```
205
+
206
+ **Functional Wrappers:**
207
+
208
+ | Function | Type | Description |
209
+ |----------|------|-------------|
210
+ | `fint(x)` | `FunctionalInt` | Enables piping for integers |
211
+ | `ffloat(x)` | `FunctionalFloat` | Enables piping for floats |
212
+ | `fstr(x)` | `FunctionalStr` | Enables piping for strings |
213
+
214
+ ---
215
+
216
+ ### 4. Built-in Filters
217
+
218
+ **Arithmetic:**
219
+ ```python
220
+ from punctional import fint, Add, Sub, Mult, Div
221
+
222
+ fint(10) | Add(5) # 15
223
+ fint(10) | Sub(3) # 7
224
+ fint(10) | Mult(2) # 20
225
+ fint(10) | Div(4) # 2.5
226
+ ```
227
+
228
+ **Comparison:**
229
+ ```python
230
+ from punctional import fint, GreaterThan, LessThan, Equals
231
+
232
+ fint(42) | GreaterThan(10) # True
233
+ fint(5) | LessThan(10) # True
234
+ fint(42) | Equals(42) # True
235
+ ```
236
+
237
+ **Logical:**
238
+ ```python
239
+ from punctional import fint, AndFilter, OrFilter, NotFilter, GreaterThan, LessThan
240
+
241
+ # All conditions must pass
242
+ fint(42) | AndFilter(GreaterThan(10), LessThan(100)) # True
243
+
244
+ # At least one condition must pass
245
+ fint(5) | OrFilter(LessThan(3), GreaterThan(3)) # True
246
+
247
+ # Negate a condition
248
+ fint(5) | NotFilter(Equals(0)) # True
249
+ ```
250
+
251
+ **String:**
252
+ ```python
253
+ from punctional import fstr, ToUpper, ToLower, Contains, Mult
254
+
255
+ fstr("hello") | ToUpper() # 'HELLO'
256
+ fstr("WORLD") | ToLower() # 'world'
257
+ fstr("hello world") | Contains("world") # True
258
+ fstr("ha") | Mult(3) # 'hahaha'
259
+ ```
260
+
261
+ **List Operations:**
262
+ ```python
263
+ from punctional import Map, FilterList, Mult, GreaterThan
264
+
265
+ numbers = [1, 2, 3, 4, 5]
266
+
267
+ Map(Mult(2)).apply(numbers) # [2, 4, 6, 8, 10]
268
+ FilterList(GreaterThan(2)).apply(numbers) # [3, 4, 5]
269
+ ```
270
+
271
+ ---
272
+
273
+ ### 5. Composition
274
+
275
+ Create reusable pipelines with `Compose`:
276
+
277
+ ```python
278
+ from punctional import Compose, Mult, Add, fint
279
+
280
+ # Define a reusable transformation
281
+ double_then_add_ten = Compose(Mult(2), Add(10))
282
+
283
+ fint(5) | double_then_add_ten # 20 (5 * 2 + 10)
284
+ fint(10) | double_then_add_ten # 30 (10 * 2 + 10)
285
+ ```
286
+
287
+ ---
288
+
289
+ ### 6. Custom Filters
290
+
291
+ Create your own filters by extending the `Filter` base class:
292
+
293
+ ```python
294
+ from punctional import Filter, fint
295
+
296
+ class Square(Filter[int, int]):
297
+ def apply(self, value: int) -> int:
298
+ return value ** 2
299
+
300
+ class Power(Filter[int, int]):
301
+ def __init__(self, exponent: int):
302
+ self.exponent = exponent
303
+
304
+ def apply(self, value: int) -> int:
305
+ return value ** self.exponent
306
+
307
+ fint(5) | Square() # 25
308
+ fint(2) | Power(10) # 1024
309
+ ```
310
+
311
+ ---
312
+
313
+ ### 7. Functional Dataclasses
314
+
315
+ Add the `Functional` mixin to any dataclass to enable piping:
316
+
317
+ ```python
318
+ from dataclasses import dataclass
319
+ from punctional import Functional, Filter
320
+
321
+ @dataclass
322
+ class Point(Functional):
323
+ x: float
324
+ y: float
325
+
326
+ class Scale(Filter[Point, Point]):
327
+ def __init__(self, factor: float):
328
+ self.factor = factor
329
+
330
+ def apply(self, p: Point) -> Point:
331
+ return Point(p.x * self.factor, p.y * self.factor)
332
+
333
+ class Translate(Filter[Point, Point]):
334
+ def __init__(self, dx: float, dy: float):
335
+ self.dx, self.dy = dx, dy
336
+
337
+ def apply(self, p: Point) -> Point:
338
+ return Point(p.x + self.dx, p.y + self.dy)
339
+
340
+ # Chain transformations on custom types
341
+ point = Point(3, 4)
342
+ result = point | Scale(2) | Translate(10, 10) # Point(16, 18)
343
+ ```
344
+
345
+ ---
346
+
347
+ ## Complete API Reference
348
+
349
+ ### Monads
350
+
351
+ | Type | Description |
352
+ |------|-------------|
353
+ | `Option[T]` | Abstract base for optional values |
354
+ | `Some(value)` | Contains a value |
355
+ | `Nothing()` | Represents absence |
356
+ | `some(value)` | Creates `Some` or `Nothing` based on `None` check |
357
+ | `Result[T, E]` | Abstract base for success/failure |
358
+ | `Ok(value)` | Successful result |
359
+ | `Error(error)` | Failed result |
360
+ | `try_result(fn)` | Wraps a function, catching exceptions as `Error` |
361
+
362
+ ### Filters
363
+
364
+ | Filter | Input → Output | Description |
365
+ |--------|----------------|-------------|
366
+ | `Add(n)` | `number → number` | Addition |
367
+ | `Sub(n)` | `number → number` | Subtraction |
368
+ | `Mult(n)` | `number → number` | Multiplication |
369
+ | `Div(n)` | `number → number` | Division |
370
+ | `GreaterThan(n)` | `number → bool` | Greater than comparison |
371
+ | `LessThan(n)` | `number → bool` | Less than comparison |
372
+ | `Equals(n)` | `any → bool` | Equality check |
373
+ | `AndFilter(*filters)` | `T → bool` | Logical AND |
374
+ | `OrFilter(*filters)` | `T → bool` | Logical OR |
375
+ | `NotFilter(filter)` | `T → bool` | Logical NOT |
376
+ | `ToUpper()` | `str → str` | Uppercase |
377
+ | `ToLower()` | `str → str` | Lowercase |
378
+ | `Contains(s)` | `str → bool` | Substring check |
379
+ | `Map(filter)` | `list → list` | Apply filter to each element |
380
+ | `FilterList(pred)` | `list → list` | Filter elements by predicate |
381
+ | `Compose(*filters)` | `T → U` | Compose multiple filters |
382
+
383
+ ### Core Classes
384
+
385
+ | Class | Description |
386
+ |-------|-------------|
387
+ | `Filter[T, U]` | Abstract base class for all filters |
388
+ | `Functional` | Mixin that enables `\|` operator on any class |
389
+ | `FunctionalInt` | Wrapper for `int` with pipe support |
390
+ | `FunctionalFloat` | Wrapper for `float` with pipe support |
391
+ | `FunctionalStr` | Wrapper for `str` with pipe support |
392
+
393
+ ---
394
+
395
+ ## Examples
396
+
397
+ Run the included examples:
398
+
399
+ ```bash
400
+ python -m examples.basics # Introduction to all features
401
+ python -m examples.extending # Creating custom filters
402
+ python -m examples.data_transformation # Real-world patterns
403
+ python -m examples.quick_reference # Quick lookup cheatsheet
404
+ ```
405
+
406
+ ---
407
+
408
+ ## Design Principles
409
+
410
+ 1. **Explicit over implicit** — Errors are values, not exceptions
411
+ 2. **Composability** — Small, reusable units that combine easily
412
+ 3. **Type safety** — Generics provide IDE support and catch bugs early
413
+ 4. **Immutability** — Filters return new values, never mutate
414
+ 5. **Zero dependencies** — Pure Python, works everywhere
415
+
416
+ ---
417
+
418
+ ## License
419
+
420
+ MIT License — see [LICENSE](LICENSE) for details.
421
+
422
+ ---
423
+
424
+ <p align="center">
425
+ Made with ❤️ for functional programming in Python
426
+ </p>
@@ -1,4 +1,4 @@
1
- punctional/__init__.py,sha256=gylyr0Ij6x97I0vc62JzaoRszhcPUdGqwhHl03ooBKY,1271
1
+ punctional/__init__.py,sha256=0fzsSIiCBnYKBug88xYWHVCWtngDcHIAM_enuqkHmKc,1271
2
2
  punctional/arithmetic.py,sha256=TzU5wJqu0olmEyh-TuHIpMH_2mw-EFoDRxmK6kAFbUU,1071
3
3
  punctional/comparison.py,sha256=X9SzAXbNXUR5_lmPaZPaTMqM_0zwlvNPb93pcpipogI,850
4
4
  punctional/core.py,sha256=g3VuKNpfbIBoNf8OEAUDtc5bLdr-PAbbMwupnkz8wPE,2838
@@ -7,7 +7,7 @@ punctional/logical.py,sha256=3TR0Bgv-SwUjo4o177hHe8YLTr3jG4Kb13GZBGmEb9w,980
7
7
  punctional/monads.py,sha256=zDGr7GMhiGc5whNj81lQTDgxfJtBOuh8IuNGpT7gq5g,9739
8
8
  punctional/string.py,sha256=E8Ajo7_Rok55_ZNbDFQgCub0VCM4-fW_bUNswyUqDds,607
9
9
  punctional/types.py,sha256=gS8Z3XtHKT0dttuGUMFsZWMoiLKuE51cRZMOVVaNKJw,1724
10
- punctional-0.1.0.dist-info/METADATA,sha256=bht_JYz963a87ZmZVfSFspxfZAwxd67UrhPqFSOFBls,12337
11
- punctional-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
12
- punctional-0.1.0.dist-info/licenses/LICENSE,sha256=-iDh_gcypGAb576TWRWkMhTMvuAaOML4rZwKV1s8Xoo,1070
13
- punctional-0.1.0.dist-info/RECORD,,
10
+ punctional-0.1.1.dist-info/METADATA,sha256=LObRFwN3ERvb3uJBYtiinfCbWrR4d6oCq-AJ1A2bnR4,12046
11
+ punctional-0.1.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
12
+ punctional-0.1.1.dist-info/licenses/LICENSE,sha256=-iDh_gcypGAb576TWRWkMhTMvuAaOML4rZwKV1s8Xoo,1070
13
+ punctional-0.1.1.dist-info/RECORD,,
@@ -1,445 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: punctional
3
- Version: 0.1.0
4
- Summary: A functional programming framework for Python — composable filters, method chaining, and expressive data pipelines with Option and Result monads.
5
- Project-URL: Homepage, https://github.com/peghaz/punctional
6
- Project-URL: Repository, https://github.com/peghaz/punctional
7
- Project-URL: Documentation, https://github.com/peghaz/punctional#readme
8
- Project-URL: Issues, https://github.com/peghaz/punctional/issues
9
- Author-email: Mehdi Peghaz <peghaz@example.com>
10
- License: MIT
11
- License-File: LICENSE
12
- Keywords: composition,data-pipeline,filters,functional-programming,method-chaining,monads,option,pipe-operator,result
13
- Classifier: Development Status :: 4 - Beta
14
- Classifier: Intended Audience :: Developers
15
- Classifier: License :: OSI Approved :: MIT License
16
- Classifier: Operating System :: OS Independent
17
- Classifier: Programming Language :: Python :: 3
18
- Classifier: Programming Language :: Python :: 3.12
19
- Classifier: Programming Language :: Python :: 3.13
20
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
- Classifier: Typing :: Typed
22
- Requires-Python: >=3.12
23
- Description-Content-Type: text/markdown
24
-
25
- # Punctional
26
-
27
- > A functional programming framework for Python — enabling composable filters, method chaining, and expressive data pipelines.
28
-
29
- [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
30
- [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
31
-
32
- ## 🎯 What is Punctional?
33
-
34
- **Punctional** is a lightweight functional programming library that brings powerful functional programming patterns to Python. It allows you to compose operations using an intuitive pipe (`|`) operator, create reusable transformation filters, and apply common functional design patterns like Option and Result monads.
35
-
36
- Whether you're building data pipelines, validation logic, or just want cleaner, more declarative code — Punctional provides the building blocks.
37
-
38
- ## ✨ Key Features
39
-
40
- - **🔗 Pipe Operator Chaining** — Chain transformations using the intuitive `|` operator
41
- - **🧱 Composable Filters** — Create reusable, testable transformation units
42
- - **📦 Functional Wrappers** — Wrap native types (`int`, `float`, `str`) for functional operations
43
- - **🎭 Monads** — Built-in `Option` (Some/Nothing) and `Result` (Ok/Error) monads
44
- - **🔧 Extensible** — Easily create custom filters for your domain
45
- - **📋 Dataclass Support** — Make any dataclass functional with the `Functional` mixin
46
-
47
- ## 📦 Installation
48
-
49
- ```bash
50
- pip install punctional
51
- ```
52
-
53
- Or install from source:
54
-
55
- ```bash
56
- git clone https://github.com/peghaz/punctional.git
57
- cd punctional
58
- pip install -e .
59
- ```
60
-
61
- ## 🚀 Quick Start
62
-
63
- ```python
64
- from punctional import fint, fstr, Add, Mult, ToUpper, GreaterThan, AndFilter, LessThan
65
-
66
- # Arithmetic chaining
67
- result = fint(10) | Add(5) | Mult(2) # (10 + 5) * 2 = 30
68
-
69
- # String transformations
70
- text = fstr("hello") | ToUpper() | Add("!") # "HELLO!"
71
-
72
- # Logical validation
73
- is_valid = fint(42) | AndFilter(GreaterThan(10), LessThan(100)) # True
74
- ```
75
-
76
- ## 📚 Core Concepts
77
-
78
- ### Filters
79
-
80
- A **Filter** is the fundamental building block — a transformation that takes an input and produces an output:
81
-
82
- ```python
83
- from punctional import Filter, fint
84
-
85
- class Square(Filter[int, int]):
86
- def apply(self, value: int) -> int:
87
- return value ** 2
88
-
89
- # Use it
90
- result = fint(5) | Square() # 25
91
- ```
92
-
93
- ### Functional Wrappers
94
-
95
- Wrap native Python types to enable pipe operations:
96
-
97
- | Function | Type | Description |
98
- |----------|------|-------------|
99
- | `fint(x)` | `FunctionalInt` | Functional integer wrapper |
100
- | `ffloat(x)` | `FunctionalFloat` | Functional float wrapper |
101
- | `fstr(x)` | `FunctionalStr` | Functional string wrapper |
102
-
103
- ### The Pipe Operator
104
-
105
- Chain filters using the `|` operator for readable, left-to-right transformations:
106
-
107
- ```python
108
- # Instead of nested calls:
109
- result = Div(4).apply(Sub(3).apply(Mult(2).apply(Add(5).apply(10))))
110
-
111
- # Write declaratively:
112
- result = fint(10) | Add(5) | Mult(2) | Sub(3) | Div(4)
113
- ```
114
-
115
- ## 🔧 Built-in Filters
116
-
117
- ### Arithmetic Filters
118
-
119
- ```python
120
- from punctional import Add, Sub, Mult, Div
121
-
122
- fint(10) | Add(5) # 15
123
- fint(10) | Sub(3) # 7
124
- fint(10) | Mult(2) # 20
125
- fint(10) | Div(4) # 2.5
126
- ```
127
-
128
- ### Comparison Filters
129
-
130
- ```python
131
- from punctional import GreaterThan, LessThan, Equals
132
-
133
- fint(42) | GreaterThan(10) # True
134
- fint(5) | LessThan(10) # True
135
- fint(42) | Equals(42) # True
136
- ```
137
-
138
- ### Logical Filters
139
-
140
- ```python
141
- from punctional import AndFilter, OrFilter, NotFilter, GreaterThan, LessThan, Equals
142
-
143
- # AND: all conditions must be true
144
- fint(42) | AndFilter(GreaterThan(10), LessThan(100)) # True
145
-
146
- # OR: at least one condition must be true
147
- fint(5) | OrFilter(LessThan(10), GreaterThan(100)) # True
148
-
149
- # NOT: negate a condition
150
- fint(5) | NotFilter(Equals(0)) # True
151
- ```
152
-
153
- ### String Filters
154
-
155
- ```python
156
- from punctional import ToUpper, ToLower, Contains, fstr
157
-
158
- fstr("hello") | ToUpper() # "HELLO"
159
- fstr("WORLD") | ToLower() # "world"
160
- fstr("hello world") | Contains("world") # True
161
- fstr("ha") | Mult(3) # "hahaha"
162
- ```
163
-
164
- ### List Filters
165
-
166
- ```python
167
- from punctional import Map, FilterList, Reduce, Mult, GreaterThan
168
-
169
- numbers = [1, 2, 3, 4, 5]
170
-
171
- # Transform each element
172
- Map(Mult(2)).apply(numbers) # [2, 4, 6, 8, 10]
173
-
174
- # Filter elements
175
- FilterList(GreaterThan(2)).apply(numbers) # [3, 4, 5]
176
- ```
177
-
178
- ### Composition
179
-
180
- Create reusable pipelines with `Compose`:
181
-
182
- ```python
183
- from punctional import Compose, Mult, Add, fint
184
-
185
- # Create a reusable transformation
186
- double_plus_ten = Compose(Mult(2), Add(10))
187
-
188
- fint(5) | double_plus_ten # 20
189
- fint(10) | double_plus_ten # 30
190
- ```
191
-
192
- ## 🎭 Functional Design Patterns
193
-
194
- ### Option Monad (Some/Nothing)
195
-
196
- Handle nullable values without explicit `None` checks:
197
-
198
- ```python
199
- from punctional import Some, Nothing, some
200
-
201
- # Wrap a value
202
- value = Some(42)
203
- print(value.map(lambda x: x * 2)) # Some(84)
204
- print(value.get_or_else(0)) # 42
205
-
206
- # Handle absence
207
- empty = Nothing()
208
- print(empty.map(lambda x: x * 2)) # Nothing
209
- print(empty.get_or_else(0)) # 0
210
-
211
- # Auto-convert from potentially None values
212
- result = some(potentially_none_value) # Returns Nothing if None
213
- ```
214
-
215
- #### Option Operations
216
-
217
- | Method | Description |
218
- |--------|-------------|
219
- | `map(f)` | Transform value if present |
220
- | `flat_map(f)` | Chain operations returning Option |
221
- | `bind(f)` | Alias for flat_map |
222
- | `get_or_else(default)` | Get value or default |
223
- | `get_or_none()` | Get value or None |
224
- | `filter(predicate)` | Return Nothing if predicate fails |
225
- | `or_else(alternative)` | Return alternative if Nothing |
226
- | `is_some()` | Check if value is present |
227
- | `is_nothing()` | Check if value is absent |
228
-
229
- ### Result Monad (Ok/Error)
230
-
231
- Handle operations that can fail with meaningful errors:
232
-
233
- ```python
234
- from punctional import Ok, Error, try_result
235
-
236
- # Successful operation
237
- success = Ok(42)
238
- print(success.map(lambda x: x * 2)) # Ok(84)
239
-
240
- # Failed operation
241
- failure = Error("Something went wrong")
242
- print(failure.map(lambda x: x * 2)) # Error("Something went wrong")
243
-
244
- # Wrap potentially throwing functions
245
- def divide(a, b):
246
- return a / b
247
-
248
- result = try_result(lambda: divide(10, 0))
249
- # Error(ZeroDivisionError(...))
250
- ```
251
-
252
- #### Result Operations
253
-
254
- | Method | Description |
255
- |--------|-------------|
256
- | `map(f)` | Transform success value |
257
- | `map_error(f)` | Transform error value |
258
- | `flat_map(f)` | Chain operations returning Result |
259
- | `bind(f)` | Alias for flat_map |
260
- | `get_or_else(default)` | Get value or default |
261
- | `is_ok()` | Check if successful |
262
- | `is_error()` | Check if failed |
263
- | `to_option()` | Convert to Option (discards error info) |
264
-
265
- ## 🏗️ Extending the Framework
266
-
267
- ### Creating Custom Filters
268
-
269
- #### Simple Filter
270
-
271
- ```python
272
- from punctional import Filter
273
-
274
- class Increment(Filter[int, int]):
275
- def apply(self, value: int) -> int:
276
- return value + 1
277
- ```
278
-
279
- #### Parameterized Filter
280
-
281
- ```python
282
- class Power(Filter[int, int]):
283
- def __init__(self, exponent: int):
284
- self.exponent = exponent
285
-
286
- def apply(self, value: int) -> int:
287
- return value ** self.exponent
288
-
289
- fint(2) | Power(3) # 8
290
- ```
291
-
292
- #### Stateful Filter
293
-
294
- ```python
295
- class Accumulator(Filter[int, int]):
296
- def __init__(self, initial: int = 0):
297
- self.total = initial
298
-
299
- def apply(self, value: int) -> int:
300
- self.total += value
301
- return self.total
302
-
303
- acc = Accumulator()
304
- acc(5) # 5
305
- acc(10) # 15
306
- acc(3) # 18
307
- ```
308
-
309
- ### Functional Dataclasses
310
-
311
- Make any dataclass functional with the `Functional` mixin:
312
-
313
- ```python
314
- from dataclasses import dataclass
315
- from punctional import Functional, Filter
316
-
317
- @dataclass
318
- class Point(Functional):
319
- x: float
320
- y: float
321
-
322
- class ScalePoint(Filter[Point, Point]):
323
- def __init__(self, factor: float):
324
- self.factor = factor
325
-
326
- def apply(self, point: Point) -> Point:
327
- return Point(point.x * self.factor, point.y * self.factor)
328
-
329
- # Now Point supports piping!
330
- point = Point(3, 4)
331
- scaled = point | ScalePoint(2.5) # Point(7.5, 10.0)
332
- ```
333
-
334
- ## 📖 Examples
335
-
336
- The [examples/](examples/) directory contains comprehensive examples:
337
-
338
- | File | Description |
339
- |------|-------------|
340
- | [basics.py](examples/basics.py) | Basic usage and introduction to all features |
341
- | [extending.py](examples/extending.py) | Guide to creating custom filters and domain-specific extensions |
342
- | [data_transformation.py](examples/data_transformation.py) | Advanced patterns: validation pipelines, data transformations |
343
- | [quick_reference.py](examples/quick_reference.py) | Cheat sheet for quick lookup |
344
-
345
- ### Run the examples:
346
-
347
- ```bash
348
- python -m examples.basics
349
- python -m examples.extending
350
- python -m examples.data_transformation
351
- ```
352
-
353
- ## 🧪 Common Patterns
354
-
355
- ### Validation Pipeline
356
-
357
- ```python
358
- from punctional import AndFilter, Functional, Filter
359
- from dataclasses import dataclass
360
-
361
- @dataclass
362
- class Person(Functional):
363
- name: str
364
- age: int
365
- email: str
366
-
367
- class ValidateName(Filter[Person, bool]):
368
- def apply(self, person: Person) -> bool:
369
- return 1 <= len(person.name) <= 100
370
-
371
- class ValidateAge(Filter[Person, bool]):
372
- def apply(self, person: Person) -> bool:
373
- return 0 <= person.age <= 150
374
-
375
- class ValidateEmail(Filter[Person, bool]):
376
- def apply(self, person: Person) -> bool:
377
- return "@" in person.email and "." in person.email
378
-
379
- # Use the validation pipeline
380
- person = Person("Alice", 30, "alice@example.com")
381
- is_valid = person | AndFilter(ValidateName(), ValidateAge(), ValidateEmail()) # True
382
- ```
383
-
384
- ### Data Transformation Pipeline
385
-
386
- ```python
387
- class ApplyBonus(Filter[Person, Person]):
388
- def __init__(self, percentage: float):
389
- self.percentage = percentage
390
-
391
- def apply(self, person: Person) -> Person:
392
- return Person(person.name, person.age, person.email)
393
-
394
- person | ApplyBonus(10) | PromoteAge() | SaveToDatabase()
395
- ```
396
-
397
- ### List Processing Pipeline
398
-
399
- ```python
400
- from punctional import FilterList, Map, Mult
401
-
402
- class IsEven(Filter[int, bool]):
403
- def apply(self, value: int) -> bool:
404
- return value % 2 == 0
405
-
406
- numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
407
- result = FilterList(IsEven()).apply(numbers) # [2, 4, 6, 8, 10]
408
- doubled = Map(Mult(2)).apply(result) # [4, 8, 12, 16, 20]
409
- ```
410
-
411
- ### Error Handling with Result
412
-
413
- ```python
414
- from punctional import Result, Ok, Error
415
-
416
- def fetch_user(id: int) -> Result[dict, str]:
417
- if id < 0:
418
- return Error("Invalid ID")
419
- return Ok({"id": id, "name": "Alice"})
420
-
421
- result = fetch_user(42).map(lambda u: u["name"]).get_or_else("Unknown") # "Alice"
422
- result = fetch_user(-1).map(lambda u: u["name"]).get_or_else("Unknown") # "Unknown"
423
- ```
424
-
425
- ## 🎓 Design Principles
426
-
427
- 1. **Immutability** — Filters don't modify input; they return new values
428
- 2. **Composability** — Filters can be combined to create complex transformations
429
- 3. **Type Safety** — Generic types help catch errors at development time
430
- 4. **Readability** — Pipe operator makes data flow explicit and easy to follow
431
- 5. **Extensibility** — Easy to create domain-specific filters
432
-
433
- ## 🤝 Contributing
434
-
435
- Contributions are welcome! Please feel free to submit a Pull Request.
436
-
437
- ## 📄 License
438
-
439
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
440
-
441
- ---
442
-
443
- <p align="center">
444
- Made with ❤️ for functional programming enthusiasts
445
- </p>