monadc
Functional programming monads for Python with first-class pattern matching support.
Installation
pip install monadc
Option - Handle Missing Data Safely
Example: Extracting nested data from API responses without crashes.
from monadc import Option, Some, Nil
# Instead of this brittle code:
def get_user_avatar(api_response):
if (api_response and "data" in api_response and
api_response["data"] and "user" in api_response["data"] and
api_response["data"]["user"] and "profile" in api_response["data"]["user"]):
profile = api_response["data"]["user"]["profile"]
return profile.get("avatar_url", "/default.png")
return "/default.png"
# Write this:
def get_user_avatar(api_response):
return (Option(api_response.get("data"))
.flat_map(lambda data: Option(data.get("user")))
.flat_map(lambda user: Option(user.get("profile")))
.flat_map(lambda profile: Option(profile.get("avatar_url")))
.unwrap_or("/default.png"))
# Pattern matching for different cases
def handle_user_data(api_response):
user_profile = (Option(api_response.get("data"))
.flat_map(lambda data: Option(data.get("user")))
.flat_map(lambda user: Option(user.get("profile"))))
match user_profile:
case Some(profile) if profile.get("verified"):
return f"✓ Verified user: {profile['name']}"
case Some(profile):
return f"User: {profile.get('name', 'Anonymous')}"
case Nil():
return "Please log in"
Try - Exception-Safe Operations
Example: File I/O and parsing operations that can fail in multiple ways.
Note: You can also use Result/Ok/Err for Rust-style syntax with identical functionality.
from monadc import Try, Success, Failure, try_
import json
@try_
def load_user_config(username: str):
with open(f"users/{username}/config.json") as f:
return json.load(f)
@try_
def validate_theme(config: dict):
theme = config["ui"]["theme"]
if theme not in ["light", "dark", "auto"]:
raise ValueError(f"Invalid theme: {theme}")
return theme
# Chain operations that can each fail
def get_user_theme(username: str):
return (load_user_config(username)
.and_then(validate_theme)
.unwrap_or("light"))
# Pattern matching handles different failure types
def load_config_with_feedback(username: str):
result = load_user_config(username).and_then(validate_theme)
match result:
case Success(theme):
return f"Loaded theme: {theme}"
case Failure(FileNotFoundError()):
return "No config found, using defaults"
case Failure(json.JSONDecodeError()):
return "Config file corrupted, using defaults"
case Failure(KeyError()):
return "Config missing theme setting"
case Failure(ValueError() as e):
return f"Invalid config: {e}"
Either - Validation with Error Messages
Example: Form validation that collects specific error messages.
from monadc import Either, Left, Right
def validate_email(email: str) -> Either[str, str]:
if not email:
return Left("Email is required")
if "@" not in email or "." not in email:
return Left("Please enter a valid email address")
return Right(email.lower())
def validate_age(age_str: str) -> Either[str, int]:
try:
age = int(age_str)
if age < 13:
return Left("Must be at least 13 years old")
if age > 120:
return Left("Please enter a valid age")
return Right(age)
except ValueError:
return Left("Age must be a number")
# Pattern matching for comprehensive error handling
def create_user_account(form_data):
email_result = validate_email(form_data.get("email", ""))
age_result = validate_age(form_data.get("age", ""))
match (email_result, age_result):
case (Right(email), Right(age)):
return create_account(email, age)
case (Left(email_error), Right(_)):
return {"error": f"Email: {email_error}"}
case (Right(_), Left(age_error)):
return {"error": f"Age: {age_error}"}
case (Left(email_error), Left(age_error)):
return {"error": f"Email: {email_error}; Age: {age_error}"}
Key Benefits
Four functional primitives for safer code:
Option/Some/Nil- Handle missing data without None checksResult/Ok/ErrandTry/Success/Failure- Exception handling with explicit error typesEither/Left/Right- Type-safe unions for validation and error messaging
Enhanced Python integration:
- Function decorators (
@try_,@option,@result) for automatic wrapping - First-class support for
match/casepattern matching (Python 3.10+) - Full MyPy compatibility with generic type annotations
Contributing
See CLAUDE.md for development setup.
Metadata
Release files for monadc 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| monadc-0.1.0.tar.gz | 207.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| monadc-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 233.5 kB
Release files / monadc-0.1.0.tar.gz
| Download URL | monadc-0.1.0.tar.gz |
|---|---|
| Size | 207.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f2774ccac2dcd6c1f92719a54cd062ffc2df0dc6e4749f68a69800b3ace4d2f0
|
|
BLAKE2b-256 checksum How to use checksums |
185b98bfe4688c9b197a6b4d9bf1a5a933f015ad3edffd1c50ba98431c7130f5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.7.19
|
Release files / monadc-0.1.0-py3-none-any.whl
| Download URL | monadc-0.1.0-py3-none-any.whl |
|---|---|
| Size | 26.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d3e21997702d13997c97c77bb272ae985a0ce9132d52a2a93ca171f118887fca
|
|
BLAKE2b-256 checksum How to use checksums |
8f8f1523328c4bdfdce9123889c9eecd0780779cb31c1ba0ecf37d5d3d8b24f6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.7.19
|