# monadc
Functional programming monads for Python with first-class pattern matching support.
## Installation
```bash
pip install monadc
```
## Option - Handle Missing Data Safely
**Example**: Extracting nested data from API responses without crashes.
```python
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.*
```python
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.
```python
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](CLAUDE.md) for development setup.
Raw data
{
"_id": null,
"home_page": null,
"name": "monadc",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "either, functional, monads, option, programming, result, rust, scala, try",
"author": "Carl You",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/18/5b/98bfe4688c9b197a6b4d9bf1a5a933f015ad3edffd1c50ba98431c7130f5/monadc-0.1.0.tar.gz",
"platform": null,
"description": "# monadc\n\nFunctional programming monads for Python with first-class pattern matching support.\n\n## Installation\n\n```bash\npip install monadc\n```\n\n## Option - Handle Missing Data Safely\n\n**Example**: Extracting nested data from API responses without crashes.\n\n```python\nfrom monadc import Option, Some, Nil\n\n# Instead of this brittle code:\ndef get_user_avatar(api_response):\n if (api_response and \"data\" in api_response and \n api_response[\"data\"] and \"user\" in api_response[\"data\"] and\n api_response[\"data\"][\"user\"] and \"profile\" in api_response[\"data\"][\"user\"]):\n profile = api_response[\"data\"][\"user\"][\"profile\"]\n return profile.get(\"avatar_url\", \"/default.png\")\n return \"/default.png\"\n\n# Write this:\ndef get_user_avatar(api_response):\n return (Option(api_response.get(\"data\"))\n .flat_map(lambda data: Option(data.get(\"user\")))\n .flat_map(lambda user: Option(user.get(\"profile\")))\n .flat_map(lambda profile: Option(profile.get(\"avatar_url\")))\n .unwrap_or(\"/default.png\"))\n\n# Pattern matching for different cases\ndef handle_user_data(api_response):\n user_profile = (Option(api_response.get(\"data\"))\n .flat_map(lambda data: Option(data.get(\"user\")))\n .flat_map(lambda user: Option(user.get(\"profile\"))))\n \n match user_profile:\n case Some(profile) if profile.get(\"verified\"):\n return f\"\u2713 Verified user: {profile['name']}\"\n case Some(profile):\n return f\"User: {profile.get('name', 'Anonymous')}\"\n case Nil():\n return \"Please log in\"\n```\n\n## Try - Exception-Safe Operations\n\n**Example**: File I/O and parsing operations that can fail in multiple ways.\n\n*Note: You can also use `Result/Ok/Err` for Rust-style syntax with identical functionality.*\n\n```python\nfrom monadc import Try, Success, Failure, try_\nimport json\n\n@try_\ndef load_user_config(username: str):\n with open(f\"users/{username}/config.json\") as f:\n return json.load(f)\n\n@try_\ndef validate_theme(config: dict):\n theme = config[\"ui\"][\"theme\"]\n if theme not in [\"light\", \"dark\", \"auto\"]:\n raise ValueError(f\"Invalid theme: {theme}\")\n return theme\n\n# Chain operations that can each fail\ndef get_user_theme(username: str):\n return (load_user_config(username)\n .and_then(validate_theme)\n .unwrap_or(\"light\"))\n\n# Pattern matching handles different failure types\ndef load_config_with_feedback(username: str):\n result = load_user_config(username).and_then(validate_theme)\n \n match result:\n case Success(theme):\n return f\"Loaded theme: {theme}\"\n case Failure(FileNotFoundError()):\n return \"No config found, using defaults\"\n case Failure(json.JSONDecodeError()):\n return \"Config file corrupted, using defaults\" \n case Failure(KeyError()):\n return \"Config missing theme setting\"\n case Failure(ValueError() as e):\n return f\"Invalid config: {e}\"\n```\n\n## Either - Validation with Error Messages\n\n**Example**: Form validation that collects specific error messages.\n\n```python\nfrom monadc import Either, Left, Right\n\ndef validate_email(email: str) -> Either[str, str]:\n if not email:\n return Left(\"Email is required\")\n if \"@\" not in email or \".\" not in email:\n return Left(\"Please enter a valid email address\")\n return Right(email.lower())\n\ndef validate_age(age_str: str) -> Either[str, int]:\n try:\n age = int(age_str)\n if age < 13:\n return Left(\"Must be at least 13 years old\")\n if age > 120:\n return Left(\"Please enter a valid age\")\n return Right(age)\n except ValueError:\n return Left(\"Age must be a number\")\n\n# Pattern matching for comprehensive error handling\ndef create_user_account(form_data):\n email_result = validate_email(form_data.get(\"email\", \"\"))\n age_result = validate_age(form_data.get(\"age\", \"\"))\n \n match (email_result, age_result):\n case (Right(email), Right(age)):\n return create_account(email, age)\n case (Left(email_error), Right(_)):\n return {\"error\": f\"Email: {email_error}\"}\n case (Right(_), Left(age_error)):\n return {\"error\": f\"Age: {age_error}\"}\n case (Left(email_error), Left(age_error)):\n return {\"error\": f\"Email: {email_error}; Age: {age_error}\"}\n```\n\n\n## Key Benefits\n\n**Four functional primitives for safer code:**\n- `Option/Some/Nil` - Handle missing data without None checks\n- `Result/Ok/Err` and `Try/Success/Failure` - Exception handling with explicit error types \n- `Either/Left/Right` - Type-safe unions for validation and error messaging\n\n**Enhanced Python integration:**\n- Function decorators (`@try_`, `@option`, `@result`) for automatic wrapping\n- First-class support for `match/case` pattern matching (Python 3.10+)\n- Full MyPy compatibility with generic type annotations\n\n## Contributing\n\nSee [CLAUDE.md](CLAUDE.md) for development setup.\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Comprehensive functional programming monads for Python - Option, Either, Try, Result types with full MyPy support",
"version": "0.1.0",
"project_urls": {
"Homepage": "https://github.com/carlyou/monadc",
"Issues": "https://github.com/carlyou/monadc/issues",
"Repository": "https://github.com/carlyou/monadc.git"
},
"split_keywords": [
"either",
" functional",
" monads",
" option",
" programming",
" result",
" rust",
" scala",
" try"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "8f8f1523328c4bdfdce9123889c9eecd0780779cb31c1ba0ecf37d5d3d8b24f6",
"md5": "559b8c213de54a5955cdb96ab85e78c7",
"sha256": "d3e21997702d13997c97c77bb272ae985a0ce9132d52a2a93ca171f118887fca"
},
"downloads": -1,
"filename": "monadc-0.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "559b8c213de54a5955cdb96ab85e78c7",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 26391,
"upload_time": "2025-08-20T06:01:33",
"upload_time_iso_8601": "2025-08-20T06:01:33.063983Z",
"url": "https://files.pythonhosted.org/packages/8f/8f/1523328c4bdfdce9123889c9eecd0780779cb31c1ba0ecf37d5d3d8b24f6/monadc-0.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "185b98bfe4688c9b197a6b4d9bf1a5a933f015ad3edffd1c50ba98431c7130f5",
"md5": "492cf1477a4317128529690dbf8e4ee7",
"sha256": "f2774ccac2dcd6c1f92719a54cd062ffc2df0dc6e4749f68a69800b3ace4d2f0"
},
"downloads": -1,
"filename": "monadc-0.1.0.tar.gz",
"has_sig": false,
"md5_digest": "492cf1477a4317128529690dbf8e4ee7",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 207117,
"upload_time": "2025-08-20T06:01:34",
"upload_time_iso_8601": "2025-08-20T06:01:34.670708Z",
"url": "https://files.pythonhosted.org/packages/18/5b/98bfe4688c9b197a6b4d9bf1a5a933f015ad3edffd1c50ba98431c7130f5/monadc-0.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-20 06:01:34",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "carlyou",
"github_project": "monadc",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "monadc"
}