Skip to main content

Comprehensive functional programming monads for Python - Option, Either, Try, Result types with full MyPy support

Project description

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 checks
  • Result/Ok/Err and Try/Success/Failure - Exception handling with explicit error types
  • Either/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/case pattern matching (Python 3.10+)
  • Full MyPy compatibility with generic type annotations

Contributing

See CLAUDE.md for development setup.

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

monadc-0.1.0.tar.gz (207.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

monadc-0.1.0-py3-none-any.whl (26.4 kB view details)

Uploaded Python 3

File details

Details for the file monadc-0.1.0.tar.gz.

File metadata

  • Download URL: monadc-0.1.0.tar.gz
  • Upload date:
  • Size: 207.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.19

File hashes

Hashes for monadc-0.1.0.tar.gz
Algorithm Hash digest
SHA256 f2774ccac2dcd6c1f92719a54cd062ffc2df0dc6e4749f68a69800b3ace4d2f0
MD5 492cf1477a4317128529690dbf8e4ee7
BLAKE2b-256 185b98bfe4688c9b197a6b4d9bf1a5a933f015ad3edffd1c50ba98431c7130f5

See more details on using hashes here.

File details

Details for the file monadc-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: monadc-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 26.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.7.19

File hashes

Hashes for monadc-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d3e21997702d13997c97c77bb272ae985a0ce9132d52a2a93ca171f118887fca
MD5 559b8c213de54a5955cdb96ab85e78c7
BLAKE2b-256 8f8f1523328c4bdfdce9123889c9eecd0780779cb31c1ba0ecf37d5d3d8b24f6

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page