A functional programming framework for Python — composable filters, method chaining, and expressive data pipelines with Option and Result monads.
Project description
Punctional
Functional programming for Python — monads, composable filters, and expressive data pipelines.
What is Punctional?
Punctional brings robust functional programming patterns to Python. It provides:
- Monads for safe error handling and null-safety (
Option,Result) - Composable filters that chain with the pipe (
|) operator - Functional wrappers for native types enabling method chaining
No dependencies. Pure Python. Type-safe.
Installation
pip install punctional
Or from source:
git clone https://github.com/peghaz/punctional.git
cd punctional
pip install -e .
Core Features
1. Option Monad — Safe Null Handling
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.
from punctional import Some, Nothing, some
# Wrap existing values
user_name = Some("Alice")
print(user_name.map(str.upper)) # Some('ALICE')
print(user_name.get_or_else("Unknown")) # 'Alice'
# Handle missing values safely
missing = Nothing()
print(missing.map(str.upper)) # Nothing
print(missing.get_or_else("Unknown")) # 'Unknown'
# Auto-convert from potentially None values
def find_user(user_id):
return {"alice": "Alice"}.get(user_id)
result = some(find_user("bob")) # Nothing (because get returns None)
result = some(find_user("alice")) # Some('Alice')
Option Methods:
| Method | Description |
|---|---|
map(f) |
Transform the value if present |
flat_map(f) / bind(f) |
Chain operations that return Option |
filter(predicate) |
Return Nothing if predicate fails |
get_or_else(default) |
Get value or return default |
get_or_none() |
Get value or None |
or_else(alternative) |
Return alternative Option if Nothing |
is_some() / is_nothing() |
Check presence |
Chaining with Option:
from punctional import Some, Nothing
def parse_int(s: str) -> Option[int]:
try:
return Some(int(s))
except ValueError:
return Nothing()
def double(x: int) -> Option[int]:
return Some(x * 2)
# Chain operations safely
result = Some("42").flat_map(parse_int).flat_map(double) # Some(84)
result = Some("abc").flat_map(parse_int).flat_map(double) # Nothing
2. Result Monad — Explicit Error Handling
The Result type represents either success (Ok) or failure (Error). Unlike exceptions, errors are explicit in the type signature and must be handled.
from punctional import Ok, Error, try_result
# Explicit success and failure
success = Ok(42)
failure = Error("Something went wrong")
print(success.map(lambda x: x * 2)) # Ok(84)
print(failure.map(lambda x: x * 2)) # Error('Something went wrong')
print(success.get_or_else(0)) # 42
print(failure.get_or_else(0)) # 0
Wrapping exceptions with try_result:
from punctional import try_result
def divide(a, b):
return a / b
result = try_result(lambda: divide(10, 2)) # Ok(5.0)
result = try_result(lambda: divide(10, 0)) # Error(ZeroDivisionError(...))
# With custom error handler
result = try_result(
lambda: divide(10, 0),
error_handler=lambda e: f"Division failed: {e}"
) # Error('Division failed: division by zero')
Result Methods:
| Method | Description |
|---|---|
map(f) |
Transform success value |
map_error(f) |
Transform error value |
flat_map(f) / bind(f) |
Chain operations returning Result |
get_or_else(default) |
Get value or default |
is_ok() / is_error() |
Check success/failure |
to_option() |
Convert to Option (discards error info) |
Railway-oriented programming:
from punctional import Result, Ok, Error
def validate_age(age: int) -> Result[int, str]:
if age < 0:
return Error("Age cannot be negative")
if age > 150:
return Error("Age seems unrealistic")
return Ok(age)
def validate_name(name: str) -> Result[str, str]:
if not name.strip():
return Error("Name cannot be empty")
return Ok(name.strip())
# Chain validations
age_result = validate_age(25).map(lambda a: f"Age: {a}") # Ok('Age: 25')
age_result = validate_age(-5).map(lambda a: f"Age: {a}") # Error('Age cannot be negative')
3. Pipe Operator & Filters
Chain transformations using the | operator for readable, left-to-right data flow.
from punctional import fint, fstr, ffloat, Add, Mult, Sub, Div, ToUpper
# Arithmetic chaining
result = fint(10) | Add(5) | Mult(2) | Sub(3) # (10 + 5) * 2 - 3 = 27
# String transformations
text = fstr("hello") | ToUpper() | Add(" WORLD!") # 'HELLO WORLD!'
# Float operations
value = ffloat(10.0) | Div(4) | Mult(2) # 5.0
Functional Wrappers:
| Function | Type | Description |
|---|---|---|
fint(x) |
FunctionalInt |
Enables piping for integers |
ffloat(x) |
FunctionalFloat |
Enables piping for floats |
fstr(x) |
FunctionalStr |
Enables piping for strings |
4. Built-in Filters
Arithmetic:
from punctional import fint, Add, Sub, Mult, Div
fint(10) | Add(5) # 15
fint(10) | Sub(3) # 7
fint(10) | Mult(2) # 20
fint(10) | Div(4) # 2.5
Comparison:
from punctional import fint, GreaterThan, LessThan, Equals
fint(42) | GreaterThan(10) # True
fint(5) | LessThan(10) # True
fint(42) | Equals(42) # True
Logical:
from punctional import fint, AndFilter, OrFilter, NotFilter, GreaterThan, LessThan
# All conditions must pass
fint(42) | AndFilter(GreaterThan(10), LessThan(100)) # True
# At least one condition must pass
fint(5) | OrFilter(LessThan(3), GreaterThan(3)) # True
# Negate a condition
fint(5) | NotFilter(Equals(0)) # True
String:
from punctional import fstr, ToUpper, ToLower, Contains, Mult
fstr("hello") | ToUpper() # 'HELLO'
fstr("WORLD") | ToLower() # 'world'
fstr("hello world") | Contains("world") # True
fstr("ha") | Mult(3) # 'hahaha'
List Operations:
from punctional import Map, FilterList, Mult, GreaterThan
numbers = [1, 2, 3, 4, 5]
Map(Mult(2)).apply(numbers) # [2, 4, 6, 8, 10]
FilterList(GreaterThan(2)).apply(numbers) # [3, 4, 5]
5. Composition
Create reusable pipelines with Compose:
from punctional import Compose, Mult, Add, fint
# Define a reusable transformation
double_then_add_ten = Compose(Mult(2), Add(10))
fint(5) | double_then_add_ten # 20 (5 * 2 + 10)
fint(10) | double_then_add_ten # 30 (10 * 2 + 10)
6. Custom Filters
Create your own filters by extending the Filter base class:
from punctional import Filter, fint
class Square(Filter[int, int]):
def apply(self, value: int) -> int:
return value ** 2
class Power(Filter[int, int]):
def __init__(self, exponent: int):
self.exponent = exponent
def apply(self, value: int) -> int:
return value ** self.exponent
fint(5) | Square() # 25
fint(2) | Power(10) # 1024
7. Functional Dataclasses
Add the Functional mixin to any dataclass to enable piping:
from dataclasses import dataclass
from punctional import Functional, Filter
@dataclass
class Point(Functional):
x: float
y: float
class Scale(Filter[Point, Point]):
def __init__(self, factor: float):
self.factor = factor
def apply(self, p: Point) -> Point:
return Point(p.x * self.factor, p.y * self.factor)
class Translate(Filter[Point, Point]):
def __init__(self, dx: float, dy: float):
self.dx, self.dy = dx, dy
def apply(self, p: Point) -> Point:
return Point(p.x + self.dx, p.y + self.dy)
# Chain transformations on custom types
point = Point(3, 4)
result = point | Scale(2) | Translate(10, 10) # Point(16, 18)
Complete API Reference
Monads
| Type | Description |
|---|---|
Option[T] |
Abstract base for optional values |
Some(value) |
Contains a value |
Nothing() |
Represents absence |
some(value) |
Creates Some or Nothing based on None check |
Result[T, E] |
Abstract base for success/failure |
Ok(value) |
Successful result |
Error(error) |
Failed result |
try_result(fn) |
Wraps a function, catching exceptions as Error |
Filters
| Filter | Input → Output | Description |
|---|---|---|
Add(n) |
number → number |
Addition |
Sub(n) |
number → number |
Subtraction |
Mult(n) |
number → number |
Multiplication |
Div(n) |
number → number |
Division |
GreaterThan(n) |
number → bool |
Greater than comparison |
LessThan(n) |
number → bool |
Less than comparison |
Equals(n) |
any → bool |
Equality check |
AndFilter(*filters) |
T → bool |
Logical AND |
OrFilter(*filters) |
T → bool |
Logical OR |
NotFilter(filter) |
T → bool |
Logical NOT |
ToUpper() |
str → str |
Uppercase |
ToLower() |
str → str |
Lowercase |
Contains(s) |
str → bool |
Substring check |
Map(filter) |
list → list |
Apply filter to each element |
FilterList(pred) |
list → list |
Filter elements by predicate |
Compose(*filters) |
T → U |
Compose multiple filters |
Core Classes
| Class | Description |
|---|---|
Filter[T, U] |
Abstract base class for all filters |
Functional |
Mixin that enables | operator on any class |
FunctionalInt |
Wrapper for int with pipe support |
FunctionalFloat |
Wrapper for float with pipe support |
FunctionalStr |
Wrapper for str with pipe support |
Examples
Run the included examples:
python -m examples.basics # Introduction to all features
python -m examples.extending # Creating custom filters
python -m examples.data_transformation # Real-world patterns
python -m examples.quick_reference # Quick lookup cheatsheet
Design Principles
- Explicit over implicit — Errors are values, not exceptions
- Composability — Small, reusable units that combine easily
- Type safety — Generics provide IDE support and catch bugs early
- Immutability — Filters return new values, never mutate
- Zero dependencies — Pure Python, works everywhere
License
MIT License — see LICENSE for details.
Made with ❤️ for functional programming in Python
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file punctional-0.1.2.tar.gz.
File metadata
- Download URL: punctional-0.1.2.tar.gz
- Upload date:
- Size: 9.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.19
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cbeccbfb38944990d2a390f7f0108a046383043425453076158616092bf99124
|
|
| MD5 |
e4671f79e44e19e4247b7347eef1248b
|
|
| BLAKE2b-256 |
f7ea732f6806de40d728dffe399937f968ecf6e0eb555b82c814cb9c5fb6fe2e
|
File details
Details for the file punctional-0.1.2-py3-none-any.whl.
File metadata
- Download URL: punctional-0.1.2-py3-none-any.whl
- Upload date:
- Size: 12.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.19
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a9ad8ed35de3584acadd06ecca48d3d74929effc7508d3ce3757123f2c7d32ff
|
|
| MD5 |
73cf674ac205b6da03a4fe60c36b9711
|
|
| BLAKE2b-256 |
834e22b8d00a93911a9ed530dfcef276743386e4277e8865b57f8f5f274590e7
|