{"id":43422274,"url":"https://github.com/kamalfarahani/katharos","last_synced_at":"2026-02-02T18:54:45.400Z","repository":{"id":330184885,"uuid":"1069680821","full_name":"kamalfarahani/katharos","owner":"kamalfarahani","description":"A library providing useful types and functions for functional programming in Python","archived":false,"fork":false,"pushed_at":"2025-12-31T17:50:11.000Z","size":496,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-01T10:28:45.795Z","etag":null,"topics":["functional-programming","monads","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kamalfarahani.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-04T12:12:37.000Z","updated_at":"2025-12-29T11:48:13.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/kamalfarahani/katharos","commit_stats":null,"previous_names":["kamalfarahani/katharos"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/kamalfarahani/katharos","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kamalfarahani%2Fkatharos","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kamalfarahani%2Fkatharos/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kamalfarahani%2Fkatharos/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kamalfarahani%2Fkatharos/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kamalfarahani","download_url":"https://codeload.github.com/kamalfarahani/katharos/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kamalfarahani%2Fkatharos/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29017937,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-02T18:51:31.335Z","status":"ssl_error","status_checked_at":"2026-02-02T18:49:20.777Z","response_time":58,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["functional-programming","monads","python"],"created_at":"2026-02-02T18:54:44.850Z","updated_at":"2026-02-02T18:54:45.389Z","avatar_url":"https://github.com/kamalfarahani.png","language":"Python","funding_links":[],"categories":["Awesome Functional Python"],"sub_categories":["Libraries"],"readme":"# Katharos\n\nKatharos is a functional programming library for Python that provides algebraic abstractions like Semigroups, Monoids, Functors, Applicatives, and Monads, along with immutable data structures to enable composable, type-safe, and side-effect-free code.\n\n\u003cimg src=\"./logo.png\" alt=\"logo\" width=\"300\" height=\"300\"\u003e\n\n## Installation\n\n```bash\npip install katharos\n```\n\n## Modules\n\n- `algebra`: Provides a set of algebraic structures for functional programming.\n- `ds`: Provides a set of data structures for functional programming.\n- `functools`: Provides a set of functional programming tools.\n\n## Algebra\nThe `algebra` module provides fundamental algebraic structures commonly used in functional programming:\n\n- **Semigroup**: A type with an associative binary operation\n- **Monoid**: A type with an associative binary operation and an identity element\n- **Functor**: A type that can be mapped over\n- **Applicative**: A functor with application, allowing functions within a context to be applied to values within a context\n- **Monad**: A structure that represents computations as a series of steps\n\nThese abstractions enable composable, reusable code patterns and help manage side effects in a pure functional style.\n\n\n### Semigroup\n\nA **Semigroup** is a fundamental algebraic structure that consists of:\n\n1. A set of values of type `S`\n2. An associative binary operation `op` (represented by the `@` operator)\n\nUnlike a Monoid, a Semigroup does **not** require an identity element. This makes it more general but less powerful for certain operations like folding empty collections.\n\n**Mathematical Properties:**\n- **Associativity**: `(a @ b) @ c = a @ (b @ c)` for all `a`, `b`, `c`\n\n**Implementation:**\n\nTo create a Semigroup, inherit from the `Semigroup` class and implement:\n- `op(self, other)`: The associative binary operation\n\n**Example 1: Creating a Custom Semigroup (Max)**\n\n```python\nfrom katharos.algebra import Semigroup\n\nclass Max(Semigroup[\"Max\"]):\n    \"\"\"A Semigroup that keeps the maximum value.\"\"\"\n    \n    def __init__(self, value: int) -\u003e None:\n        self.value = value\n    \n    def op(self, other: 'Max') -\u003e 'Max':\n        \"\"\"Combine two Max values by taking the maximum.\"\"\"\n        return Max(max(self.value, other.value))\n    \n    def __eq__(self, other: object) -\u003e bool:\n        return isinstance(other, Max) and self.value == other.value\n    \n    def __repr__(self) -\u003e str:\n        return f\"Max({self.value})\"\n\n# Using the custom Semigroup\na = Max(5)\nb = Max(10)\nc = Max(3)\n\n# Semigroup operation using @ operator\nresult = a @ b  # Max(10)\n\n# Associativity property holds\nassert (a @ b) @ c == a @ (b @ c)  # max(max(5,10),3) = max(5,max(10,3)) = 10\n\n# Note: No identity element exists for Max over all integers\n# (there's no single value i such that max(x, i) = x for all x)\n```\n\n**Example 2: NonEmptyList as a Semigroup**\n\n```python\nfrom katharos.ds.list import NonEmptyList\n\n# NonEmptyList is a Semigroup (but not a Monoid, since it can't be empty)\nlist1 = NonEmptyList(head=1, tail=[2, 3])\nlist2 = NonEmptyList(head=4, tail=[5])\n\n# Concatenation using @ operator\nresult = list1 @ list2  # NonEmptyList([1, 2, 3, 4, 5])\n\n# Associativity holds\nlist3 = NonEmptyList(head=6, tail=[7])\nassert (list1 @ list2) @ list3 == list1 @ (list2 @ list3)\n\n# NonEmptyList guarantees at least one element\n# This is useful when you need to ensure non-emptiness at the type level\n```\n\n**Key Differences from Monoid:**\n- **No identity element**: Semigroups don't have a neutral element\n- **Cannot fold empty collections**: Without an identity, you need at least one element to start\n- **More general**: Every Monoid is a Semigroup, but not every Semigroup is a Monoid\n- **Use cases**: Useful when an identity element doesn't make sense (e.g., Max, Min, NonEmptyList)\n\n### Monoid\n\nA **Monoid** is an algebraic structure that extends **Semigroup** by adding an identity element. It consists of:\n\n1. A set of values of type `M`\n2. An associative binary operation `op` (represented by the `@` operator)\n3. An identity element that acts as a neutral element for the operation\n\n**Mathematical Properties:**\n- **Associativity**: `(a @ b) @ c = a @ (b @ c)` for all `a`, `b`, `c`\n- **Identity**: `a @ identity() = a` and `identity() @ a = a` for all `a`\n\n**Implementation:**\n\nTo create a Monoid, inherit from the `Monoid` class and implement:\n- `op(self, other)`: The associative binary operation\n- `identity()`: A static method returning the identity element\n\n**Example 1: Creating a Custom Monoid (Sum)**\n\n```python\nfrom katharos.algebra import Monoid\n\nclass Sum(Monoid[\"Sum\"]):\n    \"\"\"A Monoid for integer addition with 0 as identity.\"\"\"\n    \n    def __init__(self, value: int) -\u003e None:\n        self.value = value\n    \n    def op(self, other: 'Sum') -\u003e 'Sum':\n        \"\"\"Combine two Sum values by adding their integers.\"\"\"\n        return Sum(self.value + other.value)\n    \n    @staticmethod\n    def identity() -\u003e 'Sum':\n        \"\"\"Return the identity element (0 for addition).\"\"\"\n        return Sum(0)\n    \n    def __eq__(self, other: object) -\u003e bool:\n        return isinstance(other, Sum) and self.value == other.value\n    \n    def __repr__(self) -\u003e str:\n        return f\"Sum({self.value})\"\n\n# Using the custom Monoid\na = Sum(5)\nb = Sum(10)\nc = Sum(3)\n\n# Monoid operation using @ operator\nresult = a @ b  # Sum(15)\n\n# Identity property\nidentity = Sum.identity()  # Sum(0)\nassert a @ identity == a  # 5 + 0 = 5\nassert identity @ a == a  # 0 + 5 = 5\n\n# Associativity property\nassert (a @ b) @ c == a @ (b @ c)  # (5+10)+3 = 5+(10+3) = 18\n```\n\n**Example 2: ImmutableList as a Monoid**\n\n```python\nfrom katharos.ds import ImmutableList\n\n# The identity element is an empty list\nempty = ImmutableList.identity()  # ImmutableList([])\n\n# Concatenation is the monoid operation\nlist1 = ImmutableList([1, 2, 3])\nlist2 = ImmutableList([4, 5])\n\n# Using the @ operator (monoid operation)\nresult = list1 @ list2  # ImmutableList([1, 2, 3, 4, 5])\n\n# Identity property holds\nassert list1 @ empty == list1  # Left identity\nassert empty @ list1 == list1  # Right identity\n\n# Associativity holds\nlist3 = ImmutableList([6, 7])\nassert (list1 @ list2) @ list3 == list1 @ (list2 @ list3)\n```\n\n**Example 3: MonoidMaybe for Optional Values**\n\n```python\nfrom katharos.ds.maybe import Maybe, Just, Nothing, MonoidMaybe\n\n# MonoidMaybe combines Maybe values containing Semigroup elements\n# Identity is Nothing\nidentity = MonoidMaybe.identity()  # MonoidMaybe(Nothing())\n\n# Combining with Nothing returns the other value\nm1 = MonoidMaybe(Just(ImmutableList([1, 2])))\nm2 = MonoidMaybe(Nothing())\nresult = m1 @ m2  # MonoidMaybe(Just(ImmutableList([1, 2])))\n\n# Combining two Just values combines their contents\nm3 = MonoidMaybe(Just(ImmutableList([3, 4])))\nm4 = MonoidMaybe(Just(ImmutableList([5, 6])))\nresult = m3 @ m4  # MonoidMaybe(Just(ImmutableList([3, 4, 5, 6])))\n```\n\n### Functor\n\nA **Functor** is a type that can be mapped over, allowing you to apply a function to values inside a computational context without changing the structure itself. It's one of the most fundamental abstractions in functional programming.\n\n**Core Concept:**\n- A Functor wraps values in a context (e.g., `Maybe[A]`, `List[A]`, `Result[A, E]`)\n- It provides `fmap` to apply a function to the wrapped value(s) while preserving the context\n- The structure remains unchanged; only the values are transformed\n\n**Mathematical Laws:**\n\nFunctors must satisfy two laws:\n\n1. **Identity Law**: `fmap(id) = id`\n   - Mapping the identity function should return the same functor\n   \n2. **Composition Law**: `fmap(g ∘ f) = fmap(g) ∘ fmap(f)`\n   - Mapping a composition of functions should be the same as composing the mapped functions\n\n**Implementation:**\n\nTo create a Functor, inherit from the `Functor[A]` class and implement:\n- `fmap[B](self, f: Callable[[A], B]) -\u003e Functor[B]`: Map a function over the functor's contents\n\n**Example 1: Creating a Custom Functor (Box)**\n\n```python\nfrom katharos.algebra import Functor\nfrom collections.abc import Callable\n\nclass Box[A](Functor[\"Box\", A]):\n    \"\"\"A simple container that wraps a single value.\"\"\"\n    \n    def __init__(self, value: A) -\u003e None:\n        self.value = value\n    \n    def fmap[B](self, f: Callable[[A], B]) -\u003e 'Box[B]':\n        \"\"\"Apply a function to the wrapped value.\"\"\"\n        return Box(f(self.value))\n    \n    def __eq__(self, other: object) -\u003e bool:\n        return isinstance(other, Box) and self.value == other.value\n    \n    def __repr__(self) -\u003e str:\n        return f\"Box({self.value!r})\"\n\n# Using the custom Functor\nbox = Box(5)\n\n# Map a function over the value\nresult = box.fmap(lambda x: x * 2)  # Box(10)\n\n# Functor laws verification\n# Identity law: fmap(id) = id\nidentity = lambda x: x\nassert box.fmap(identity) == box\n\n# Composition law: fmap(g . f) = fmap(g) . fmap(f)\nf = lambda x: x + 3\ng = lambda x: x * 2\nassert box.fmap(lambda x: g(f(x))) == box.fmap(f).fmap(g)\n```\n\n**Example 2: Maybe as a Functor**\n\n```python\nfrom katharos.ds.maybe import Maybe, Just, Nothing\n\n# Maybe handles optional values\njust_value = Just(10)\nnothing_value = Nothing()\n\n# fmap applies the function only if a value exists\nresult1 = just_value.fmap(lambda x: x * 2)  # Just(20)\nresult2 = nothing_value.fmap(lambda x: x * 2)  # Nothing()\n\n# Chain multiple transformations\nresult = Just(5).fmap(lambda x: x + 3).fmap(lambda x: x * 2)  # Just(16)\n\n# Safe computation without null checks\ndef safe_divide(x: int) -\u003e Maybe[float]:\n    return Just(10.0 / x) if x != 0 else Nothing()\n\n# Using fmap to transform the result\nresult = safe_divide(2).fmap(lambda x: x + 1)  # Just(6.0)\nresult = safe_divide(0).fmap(lambda x: x + 1)  # Nothing()\n```\n\n**Example 3: Result as a Functor for Error Handling**\n\n```python\nfrom katharos.ds import Result, Success, Failure\n\n# Result handles computations that can fail\nsuccess = Success(42)\nfailure = Failure(ValueError(\"Something went wrong\"))\n\n# fmap applies the function only to successful values\nresult1 = success.fmap(lambda x: x * 2)  # Success(84)\nresult2 = failure.fmap(lambda x: x * 2)  # Failure(ValueError(...))\n\n# Chain operations - errors propagate automatically\ndef parse_int(s: str) -\u003e Result[int, ValueError]:\n    try:\n        return Success(int(s))\n    except ValueError as e:\n        return Failure(e)\n\n# Transform successful results\nresult = parse_int(\"42\").fmap(lambda x: x * 2).fmap(lambda x: x + 10)  # Success(94)\nresult = parse_int(\"invalid\").fmap(lambda x: x * 2)  # Failure(ValueError(...))\n```\n\n**Example 4: ImmutableList as a Functor**\n\n```python\nfrom katharos.ds import ImmutableList\n\n# Lists are functors that map over each element\nnumbers = ImmutableList([1, 2, 3, 4, 5])\n\n# fmap applies the function to each element\ndoubled = numbers.fmap(lambda x: x * 2)  # ImmutableList([2, 4, 6, 8, 10])\nsquared = numbers.fmap(lambda x: x ** 2)  # ImmutableList([1, 4, 9, 16, 25])\n\n# Chain transformations\nresult = numbers.fmap(lambda x: x + 1).fmap(lambda x: x * 2)\n# ImmutableList([4, 6, 8, 10, 12])\n\n# Empty list preserves structure\nempty = ImmutableList([])\nresult = empty.fmap(lambda x: x * 2)  # ImmutableList([])\n```\n\n**How to Write a Subtype of Functor:**\n\nTo create your own Functor type, follow these steps:\n\n**Step 1: Define Your Type**\n\n```python\nfrom katharos.algebra import Functor\nfrom collections.abc import Callable\n\nclass MyFunctor[A](Functor[\"MyFunctor\", A]):\n    \"\"\"Your custom functor type.\"\"\"\n    \n    def __init__(self, value: A) -\u003e None:\n        self._value = value\n```\n\n\u003e Note: If your type is covariant, you should use `TypeVar` with the `covariant=True` parameter.\n\n```python\nfrom typing import TypeVar\n\nA = TypeVar('A', covariant=True)\n\nclass MyFunctor(Functor[\"MyFunctor\", A]):\n    ...\n```\n\n**Step 2: Implement the `fmap` Method**\n\n```python\n    def fmap[B](self, f: Callable[[A], B]) -\u003e 'MyFunctor[B]':\n        \"\"\"\n        Map a function over the wrapped value.\n        \n        This is the key method that defines functor behavior.\n        \n        Args:\n            f: Function to apply to the value\n            \n        Returns:\n            MyFunctor[B]: New functor with transformed value\n        \"\"\"\n        return MyFunctor(f(self._value))\n```\n\n**Common Use Cases:**\n- **Optional values**: Transform values that may or may not exist (`Maybe`)\n- **Error handling**: Transform successful results while propagating errors (`Result`)\n- **Collections**: Transform each element in a collection (`List`)\n- **Async operations**: Transform values that will be available in the future\n- **Parsing**: Transform parsed values without unwrapping the parser context\n- **Dependency injection**: Transform values in a context with dependencies\n\n### Applicative\n\nAn **Applicative** functor is a functor with additional structure that allows you to apply functions wrapped in a context to values wrapped in a context. It sits between Functors and Monads in the hierarchy of functional abstractions.\n\n**Core Concept:**\n- An Applicative extends Functor with two key operations:\n  - `pure`: Lift a plain value into the applicative context\n  - `ap`: Apply a wrapped function to a wrapped value\n- It enables combining multiple independent computations in a context\n- Unlike Monads, Applicatives don't allow the result of one computation to determine the structure of the next\n\n**Mathematical Laws:**\n\nApplicatives must satisfy four laws:\n\n1. **Identity Law**: `v ** pure(id) = v`\n   - Applying the wrapped identity function returns the same value\n   \n2. **Composition Law**: `w ** (v ** (u ** pure(compose))) = (w ** v) ** u`\n   - Function composition works as expected in the applicative context\n   \n3. **Homomorphism Law**: `pure(x) ** pure(f) = pure(f(x))`\n   - Applying a wrapped function to a wrapped value is the same as wrapping the result\n   \n4. **Interchange Law**: `pure(y) ** u = u ** pure(lambda f: f(y))`\n   - The order of evaluation doesn't matter for pure values\n\n**Implementation:**\n\nTo create an Applicative, inherit from the `Applicative[A]` class and implement:\n- `pure(x)`: A class method that wraps a value in the applicative context\n- `ap(self, wrapped_funcs)`: Apply wrapped functions to the wrapped value\n- `fmap[B](self, f)`: Inherited from Functor - map a function over the wrapped value\n\n**Example 1: Creating a Custom Applicative (Box)**\n\n```python\nfrom katharos.algebra import Applicative\nfrom collections.abc import Callable\n\nclass Box[A](Applicative[\"Box\", A]):\n    \"\"\"A simple container that wraps a single value.\"\"\"\n    \n    def __init__(self, value: A) -\u003e None:\n        self.value = value\n    \n    @classmethod\n    def pure[T](cls, x: T) -\u003e 'Box[T]':\n        \"\"\"Wrap a value in a Box.\"\"\"\n        return Box(x)\n    \n    def fmap[B](self, f: Callable[[A], B]) -\u003e 'Box[B]':\n        \"\"\"Apply a function to the wrapped value.\"\"\"\n        return Box(f(self.value))\n    \n    def ap[B](self, wrapped_funcs: Applicative['Box', Callable[[A], B]]) -\u003e 'Box[B]':\n        \"\"\"Apply a wrapped function to this Box's value.\"\"\"\n        wrapped_funcs = cast(Box[Callable[[A], B]], wrapped_funcs)\n        return Box(wrapped_funcs.value(self.value))\n    \n    def __pow__[B](self, wrapped_funcs: 'Applicative[Box, Callable[[A], B]]') -\u003e 'Box[B]':\n        \"\"\"\n        Enable the ** operator for applicative application.\n        \n        Note: When implementing your own Applicative subtype, you should\n        override this method with proper type annotations specific to your\n        type. Due to Python's type system limitations, the generic type\n        parameters don't always propagate correctly through inheritance.\n        \"\"\"\n        return self.ap(wrapped_funcs)\n    \n    def __eq__(self, other: object) -\u003e bool:\n        return isinstance(other, Box) and self.value == other.value\n    \n    def __repr__(self) -\u003e str:\n        return f\"Box({self.value!r})\"\n\n# Using the custom Applicative\ndef double(x: int) -\u003e int:\n    return x * 2\n\nvalue = Box(5)\nfunc = Box(double)\n\n# Apply wrapped function using ** operator\nresult = value ** func  # Box(10)\n\n# Using pure to lift values\npure_value = Box.pure(10)  # Box(10)\n\n# Applicative laws verification\n# Identity law\nidentity = lambda x: x\nassert value ** Box.pure(identity) == value\n\n# Homomorphism law\nf = lambda x: x * 2\nx = 5\nassert Box.pure(x) ** Box.pure(f) == Box.pure(f(x))\n```\n\n**Example 2: Maybe as an Applicative**\n\n```python\nfrom katharos.ds.maybe import Maybe, Just, Nothing\n\n# Maybe handles optional computations\n# pure lifts a value into Just\nvalue = Maybe.pure(10)  # Just(10)\n\n# Applying a wrapped function\nfunc = Just(lambda x: x * 2)\nresult = Just(5) ** func  # Just(10)\n\n# Nothing propagates through applicative operations\nresult = Just(5) ** Nothing()  # Nothing()\nresult = Nothing() ** Just(lambda x: x * 2)  # Nothing()\n\n# Combining multiple Maybe values\n# Useful for validation or combining optional values\ndef add(x: int) -\u003e Callable[[int], int]:\n    return lambda y: x + y\n\nresult = Maybe.pure(add) ** Just(3) ** Just(5)  # Just(8)\nresult = Maybe.pure(add) ** Just(3) ** Nothing()  # Nothing()\n\n# Real-world example: Form validation\ndef create_user(name: str) -\u003e Callable[[int], Callable[[str], dict]]:\n    return lambda age: lambda email: {\n        \"name\": name,\n        \"age\": age,\n        \"email\": email\n    }\n\n# All fields present\nuser = Just(\"Alice\") ** Just(30) ** Just(\"alice@example.com\") ** Maybe.pure(create_user)\n# user = Just({\"name\": \"Alice\", \"age\": 30, \"email\": \"alice@example.com\"})\n\n# Missing field\nuser = Just(\"Bob\") ** Nothing() ** Just(\"bob@example.com\") ** Maybe.pure(create_user)\n# user = Nothing()\n```\n\n**Example 3: Result as an Applicative for Error Handling**\n\n```python\nfrom typing import NamedTuple\n\nfrom katharos.ds.result import Failure, Result, Success\nfrom katharos.functools import F\n\n\nclass Person(NamedTuple):\n    name: str\n    age: int\n\n\n# Result handles computations that can fail\n# pure lifts a value into Success\nvalue = Result.pure(42)  # Success(42)\n\n# Applying wrapped functions\nfunc = Success(lambda x: x * 2)\nresult = Success(5) ** func  # Success(10)\n\n# Failures propagate\nresult = Success(5) ** Failure(ValueError(\"Error\"))  # Failure(ValueError(\"Error\"))\nresult = Failure(ValueError(\"Error\")) ** Success(\n    lambda x: x * 2\n)  # Failure(ValueError(\"Error\"))\n\n\n# Combining multiple Results - useful for validation\ndef validate_age(age: int) -\u003e Result[int, ValueError]:\n    if age \u003c 0:\n        return Failure(ValueError(\"Age cannot be negative\"))\n    if age \u003e 150:\n        return Failure(ValueError(\"Age too high\"))\n    return Success(age)\n\n\ndef validate_name(name: str) -\u003e Result[str, ValueError]:\n    if not name:\n        return Failure(ValueError(\"Name cannot be empty\"))\n    return Success(name)\n\n\n@F.curry\ndef create_person(name: str, age: int) -\u003e Person:\n    return Person(name=name, age=age)\n\n\n# All validations pass\nperson = validate_age(30) ** validate_name(\"Alice\") ** Result.pure(create_person)\n# person = Success({\"name\": \"Alice\", \"age\": 30})\n\n# One validation fails\nperson = validate_age(30) ** validate_name(\"\") ** Result.pure(create_person)\n# person = Failure(ValueError(\"Name cannot be empty\"))\n```\n\n**Example 4: ImmutableList as an Applicative**\n\n```python\nfrom collections.abc import Callable\n\nfrom katharos.ds.list import ImmutableList\n\n# ImmutableList applies functions to values in a cartesian product manner\n# pure creates a singleton list\nvalue = ImmutableList[int].pure(5)  # ImmutableList([5])\n\n# Applying wrapped functions\nfuncs = ImmutableList[Callable[[int], int]](\n    [\n        lambda x: x * 2,\n        lambda x: x + 10,\n    ]\n)\nvalues = ImmutableList[int]([1, 2, 3])\n\n# Each function is applied to each value\nresult = values**funcs\n# ImmutableList([2, 4, 6, 11, 12, 13])\n\n\n# Real-world example: Generating combinations\ndef make_url(protocol: str) -\u003e Callable[[str], Callable[[str], str]]:\n    return lambda domain: lambda path: f\"{protocol}://{domain}/{path}\"\n\n\nprotocols = ImmutableList[str]([\"http\", \"https\"])\ndomains = ImmutableList[str]([\"example.com\", \"test.com\"])\npaths = ImmutableList[str]([\"api\", \"docs\"])\n\nurls: ImmutableList[str] = paths**domains**protocols ** ImmutableList.pure(make_url)\n# ImmutableList([\n#     \"http://example.com/api\", \"http://example.com/docs\",\n#     \"http://test.com/api\", \"http://test.com/docs\",\n#     \"https://example.com/api\", \"https://example.com/docs\",\n#     \"https://test.com/api\", \"https://test.com/docs\"\n# ])\n```\n\n**How to Write a Subtype of Applicative:**\n\nTo create your own Applicative type, follow these steps:\n\n**Step 1: Define Your Type**\n\n```python\nfrom katharos.algebra import Applicative\nfrom collections.abc import Callable\n\nclass MyApplicative[A](Applicative[\"MyApplicative\", A]):\n    \"\"\"Your custom applicative type.\"\"\"\n    \n    def __init__(self, value: A) -\u003e None:\n        self._value = value\n```\n\n\u003e Note: If your type is covariant, you should use `TypeVar` with the `covariant=True` parameter.\n\n```python\nfrom typing import TypeVar\n\nA = TypeVar('A', covariant=True)\n\nclass MyApplicative(Applicative[\"MyApplicative\", A]):\n    ...\n```\n\n**Step 2: Implement the `pure` Class Method**\n\n```python\n    @classmethod\n    def pure[T](cls, x: T) -\u003e 'MyApplicative[T]':\n        \"\"\"\n        Lift a value into the applicative context.\n        \n        This should wrap the value in the minimal context.\n        \n        Args:\n            x: The value to wrap\n            \n        Returns:\n            MyApplicative[T]: The wrapped value\n        \"\"\"\n        return MyApplicative(x)\n```\n\n**Step 3: Implement the `fmap` Method (from Functor)**\n\n```python\n    def fmap[B](self, f: Callable[[A], B]) -\u003e 'MyApplicative[B]':\n        \"\"\"\n        Map a function over the wrapped value.\n        \n        Args:\n            f: Function to apply to the value\n            \n        Returns:\n            MyApplicative[B]: New applicative with transformed value\n        \"\"\"\n        return MyApplicative(f(self._value))\n```\n\n**Step 4: Implement the `ap` Method**\n\n```python\n    def ap[B](\n        self,\n        wrapped_funcs: 'Applicative[MyApplicative, Callable[[A], B]]'\n    ) -\u003e 'MyApplicative[B]':\n        \"\"\"\n        Apply wrapped functions to this applicative's value.\n        \n        This is the key method that defines applicative behavior.\n        \n        Args:\n            wrapped_funcs: An applicative containing functions\n            \n        Returns:\n            MyApplicative[B]: Result of applying the wrapped function\n        \"\"\"\n        wrapped_funcs = cast(MyApplicative[Callable[[A], B]], wrapped_funcs) # This line is needed because python doesn't support higher kinded types, also it's safe because we know an instance of `Applicative[MyApplicative, Callable[[A], B]]` is an instance of `MyApplicative[Callable[[A], B]]`\n        \n        # Extract the function and apply it to the value\n        return MyApplicative(wrapped_funcs._value(self._value))\n```\n\n**Step 5: Add Type Hint For  `__pow__`**\n\n```python\n    def __pow__[B](self, other: 'Applicative[MyApplicative, Callable[[A], B]]') -\u003e 'MyApplicative[B]':\n        return self.ap(other)\n```\n\n**Key Differences from Functor and Monad:**\n- **Functor**: Only maps functions over values (`fmap`)\n- **Applicative**: Can apply wrapped functions to wrapped values (`ap`), enabling combining multiple independent computations\n- **Monad**: Can chain dependent computations where each step depends on the previous result (`bind`)\n\n**Common Use Cases:**\n- **Validation**: Accumulate multiple validation errors\n- **Combining independent computations**: When you have multiple wrapped values to combine\n- **Parsing**: Apply parsers in sequence without dependencies\n- **Configuration**: Combine multiple configuration sources\n- **Form handling**: Validate multiple form fields independently\n\n### Monad\n\nA **Monad** is a powerful abstraction that represents computations as a series of steps. It extends Applicative with the ability to chain dependent computations, where each step can depend on the result of the previous step.\n\n**Core Concept:**\n- A Monad extends Applicative with the `bind` operation (also known as `flatMap` or `\u003e\u003e=`)\n- `bind` allows sequencing computations where the structure of the next computation depends on the value from the previous one\n- Unlike Applicatives, Monads can flatten nested structures, preventing \"layers\" from accumulating\n- The key difference: Applicatives combine independent computations, Monads chain dependent ones\n\n**Mathematical Laws:**\n\nMonads must satisfy three laws:\n\n1. **Left Identity Law**: `ret(a).bind(f) = f(a)`\n   - Wrapping a value and binding it with a function is the same as applying the function directly\n   \n2. **Right Identity Law**: `m.bind(ret) = m`\n   - Binding a monad with `ret` (or `pure`) returns the original monad\n   \n3. **Associativity Law**: `m.bind(f).bind(g) = m.bind(lambda x: f(x).bind(g))`\n   - The order of binding operations doesn't matter; chaining binds is associative\n\n**Implementation:**\n\nTo create a Monad, inherit from the `Monad[A]` class and implement:\n- `pure(x)`: A class method that wraps a value in the monadic context (inherited from Applicative)\n- `fmap[B](self, f)`: Map a function over the wrapped value (inherited from Functor)\n- `ap(self, wrapped_funcs)`: Apply wrapped functions to wrapped values (inherited from Applicative)\n- `bind[B](self, f)`: Chain a computation that returns a monad\n\n**Example 1: Creating a Custom Monad (Box)**\n\n```python\nfrom katharos.algebra import Monad\nfrom collections.abc import Callable\n\nclass Box[A](Monad[\"Box\", A]):\n    \"\"\"A simple container that wraps a single value.\"\"\"\n    \n    def __init__(self, value: A) -\u003e None:\n        self.value = value\n    \n    @classmethod\n    def pure[T](cls, x: T) -\u003e 'Box[T]':\n        \"\"\"Wrap a value in a Box.\"\"\"\n        return Box(x)\n    \n    def fmap[B](self, f: Callable[[A], B]) -\u003e 'Box[B]':\n        \"\"\"Apply a function to the wrapped value.\"\"\"\n        return Box(f(self.value))\n    \n    def ap[B](self, wrapped_funcs: 'Box[Callable[[A], B]]') -\u003e 'Box[B]':\n        \"\"\"Apply a wrapped function to this Box's value.\"\"\"\n        return Box(wrapped_funcs.value(self.value))\n    \n    def bind[B](self, f: Callable[[A], 'Box[B]']) -\u003e 'Box[B]':\n        \"\"\"\n        Chain a computation that returns a Box.\n        \n        This is the key method that makes Box a Monad.\n        Unlike fmap, which wraps the result, bind expects f to return\n        a Box, preventing nested Box[Box[B]] structures.\n        \"\"\"\n        return f(self.value)\n    \n    def __pow__[B](self, wrapped_funcs: 'Box[Callable[[A], B]]') -\u003e 'Box[B]':\n        \"\"\"\n        Enable the ** operator for applicative application.\n        \n        Note: When implementing your own Monad subtype, you should\n        override this method with proper type annotations specific to your\n        type. Due to Python's type system limitations, the generic type\n        parameters don't always propagate correctly through inheritance.\n        \"\"\"\n        return self.ap(wrapped_funcs)\n    \n    def __or__[B](self, f: Callable[[A], 'Box[B]']) -\u003e 'Box[B]':\n        \"\"\"\n        Enable the | operator for monadic bind.\n        \n        This provides a convenient infix notation for chaining computations.\n        \"\"\"\n        return self.bind(f)\n    \n    def __eq__(self, other: object) -\u003e bool:\n        return isinstance(other, Box) and self.value == other.value\n    \n    def __repr__(self) -\u003e str:\n        return f\"Box({self.value!r})\"\n\n# Using the custom Monad\nbox = Box(5)\n\n# bind chains computations that return Box\nresult = box.bind(lambda x: Box(x * 2))  # Box(10)\n\n# Using the | operator for bind\nresult = box | (lambda x: Box(x * 2))  # Box(10)\n\n# Chain multiple operations - this is where Monad shines\nresult = (Box(5)\n    | (lambda x: Box(x + 3))\n    | (lambda x: Box(x * 2)))  # Box(16)\n\n# Compare with fmap - notice the difference\n# fmap would create Box(Box(10)) if we returned Box from the function\n# bind flattens it to just Box(10)\n\n# Monad laws verification\n# Left identity: ret(a).bind(f) = f(a)\nf = lambda x: Box(x * 2)\na = 5\nassert Box.pure(a).bind(f) == f(a)\n\n# Right identity: m.bind(ret) = m\nm = Box(10)\nassert m.bind(Box.pure) == m\n\n# Associativity: m.bind(f).bind(g) = m.bind(lambda x: f(x).bind(g))\ng = lambda x: Box(x + 1)\nassert m.bind(f).bind(g) == m.bind(lambda x: f(x).bind(g))\n```\n\n**Example 2: Maybe as a Monad**\n\n```python\nfrom katharos.ds.maybe import Maybe, Just, Nothing\n\n# Maybe handles optional computations with chaining\n# pure lifts a value into Just\nvalue = Maybe.pure(10)  # Just(10)\n\n# bind chains computations that might fail\ndef safe_divide(x: float) -\u003e Maybe[float]:\n    return Just(10.0 / x) if x != 0 else Nothing()\n\ndef safe_sqrt(x: float) -\u003e Maybe[float]:\n    return Just(x ** 0.5) if x \u003e= 0 else Nothing()\n\n# Chain dependent computations using bind\nresult = Just(4.0) | safe_sqrt | (lambda x: safe_divide(x))\n# Just(5.0) because sqrt(4) = 2, then 10/2 = 5\n\n# Nothing propagates through the chain\nresult = Just(-4.0) | safe_sqrt | (lambda x: safe_divide(x))\n# Nothing() because sqrt of negative fails\n\nresult = Just(0.0) | safe_sqrt | (lambda x: safe_divide(x))\n# Nothing() because division by zero fails\n\n# Real-world example: Database queries\ndef find_user(user_id: int) -\u003e Maybe[dict]:\n    # Simulate database lookup\n    users = {1: {\"name\": \"Alice\", \"dept_id\": 10}}\n    return Just(users[user_id]) if user_id in users else Nothing()\n\ndef find_department(dept_id: int) -\u003e Maybe[dict]:\n    # Simulate database lookup\n    depts = {10: {\"name\": \"Engineering\"}}\n    return Just(depts[dept_id]) if dept_id in depts else Nothing()\n\n# Chain dependent queries\nuser_with_dept = (\n    find_user(1)\n    | (lambda user: find_department(user[\"dept_id\"]))\n)\n# Just({\"name\": \"Engineering\"})\n\n# Missing user propagates Nothing\nuser_with_dept = (\n    find_user(999)\n    | (lambda user: find_department(user[\"dept_id\"]))\n)\n# Nothing() - second query never executes\n```\n\n**Example 3: Result as a Monad for Error Handling**\n\n```python\nfrom katharos.ds import Result, Success, Failure\n\n# Result handles computations that can fail with error propagation\n# pure lifts a value into Success\nvalue = Result.pure(42)  # Success(42)\n\n# bind chains computations that can fail\ndef parse_int(s: str) -\u003e Result[int, ValueError]:\n    try:\n        return Success(int(s))\n    except ValueError as e:\n        return Failure(e)\n\ndef divide(x: int) -\u003e Result[float, ValueError]:\n    if x == 0:\n        return Failure(ValueError(\"Division by zero\"))\n    return Success(100.0 / x)\n\ndef format_result(x: float) -\u003e Result[str, Exception]:\n    return Success(f\"Result: {x:.2f}\")\n\n# Chain dependent computations using | operator\nresult = (\n    parse_int(\"10\")\n    | divide\n    | format_result\n)\n# Success(\"Result: 10.00\")\n\n# Errors propagate through the chain\nresult = (\n    parse_int(\"invalid\")\n    | divide\n    | format_result\n)\n# Failure(ValueError(\"invalid literal for int()...\"))\n\nresult = (\n    parse_int(\"0\")\n    | divide\n    | format_result\n)\n# Failure(ValueError(\"Division by zero\"))\n\n# Real-world example: File processing pipeline\ndef read_file(path: str) -\u003e Result[str, Exception]:\n    try:\n        with open(path) as f:\n            return Success(f.read())\n    except Exception as e:\n        return Failure(e)\n\ndef parse_json(content: str) -\u003e Result[dict, Exception]:\n    try:\n        import json\n        return Success(json.loads(content))\n    except Exception as e:\n        return Failure(e)\n\ndef validate_schema(data: dict) -\u003e Result[dict, ValueError]:\n    if \"name\" in data and \"age\" in data:\n        return Success(data)\n    return Failure(ValueError(\"Invalid schema\"))\n\n# Chain file operations\nresult = (\n    read_file(\"user.json\")\n    | parse_json\n    | validate_schema\n)\n# Success({...}) or Failure(...) depending on each step\n```\n\n**Example 4: ImmutableList as a Monad**\n\n```python\nfrom katharos.ds import ImmutableList\n\n# ImmutableList as a Monad represents non-deterministic computations\n# pure creates a singleton list\nvalue = ImmutableList.pure(5)  # ImmutableList([5])\n\n# bind flattens nested lists (flatMap)\ndef duplicate(x: int) -\u003e ImmutableList[int]:\n    return ImmutableList([x, x])\n\nnumbers = ImmutableList([1, 2, 3])\n\n# bind applies the function and flattens the result\nresult = numbers | duplicate\n# ImmutableList([1, 1, 2, 2, 3, 3])\n\n# Compare with fmap - it would create nested lists\nnested = numbers.fmap(duplicate)\n# ImmutableList([ImmutableList([1, 1]), ImmutableList([2, 2]), ImmutableList([3, 3])])\n\n# Real-world example: Generating combinations\ndef pairs_with(x: int) -\u003e ImmutableList[tuple[int, int]]:\n    return ImmutableList([(x, 1), (x, 2), (x, 3)])\n\nresult = ImmutableList([10, 20]) | pairs_with\n# ImmutableList([(10, 1), (10, 2), (10, 3), (20, 1), (20, 2), (20, 3)])\n\n# Nested bind for cartesian products\ndef make_pair(x: int) -\u003e ImmutableList[tuple[int, int]]:\n    return ImmutableList([2, 3, 4]).fmap(lambda y: (x, y))\n\nresult = ImmutableList([1, 2]) | make_pair\n# ImmutableList([(1, 2), (1, 3), (1, 4), (2, 2), (2, 3), (2, 4)])\n```\n\n**How to Write a Subtype of Monad:**\n\nTo create your own Monad type, follow these steps:\n\n**Step 1: Define Your Type**\n\n```python\nfrom katharos.algebra import Monad\nfrom collections.abc import Callable\n\nclass MyMonad[A](Monad[\"MyMonad\", A]):\n    \"\"\"Your custom monad type.\"\"\"\n    \n    def __init__(self, value: A) -\u003e None:\n        self._value = value\n```\n\n\u003e Note: If your type is covariant, you should use `TypeVar` with the `covariant=True` parameter.\n\n```python\nfrom typing import TypeVar\nfrom typing import cast\n\nA = TypeVar('A', covariant=True)\n\nclass MyMonad(Monad[\"MyMonad\", A]):\n    ...\n```\n\n**Step 2: Implement the `pure` Class Method (from Applicative)**\n\n```python\n    @classmethod\n    def pure[T](cls, x: T) -\u003e 'MyMonad[T]':\n        \"\"\"\n        Lift a value into the monadic context.\n        \n        This should wrap the value in the minimal context.\n        \n        Args:\n            x: The value to wrap\n            \n        Returns:\n            MyMonad[T]: The wrapped value\n        \"\"\"\n        return MyMonad(x)\n```\n\n**Step 3: Implement the `fmap` Method (from Functor)**\n\n```python\n    def fmap[B](self, f: Callable[[A], B]) -\u003e 'MyMonad[B]':\n        \"\"\"\n        Map a function over the wrapped value.\n        \n        Args:\n            f: Function to apply to the value\n            \n        Returns:\n            MyMonad[B]: New monad with transformed value\n        \"\"\"\n        return MyMonad(f(self._value))\n```\n\n**Step 4: Implement the `ap` Method (from Applicative)**\n\n```python\n    def ap[B](\n        self,\n        wrapped_funcs: Applicative['MyMonad', Callable[[A], B]]\n    ) -\u003e 'MyMonad[B]':\n        \"\"\"\n        Apply wrapped functions to this monad's value.\n        \n        Args:\n            wrapped_funcs: A monad containing functions\n            \n        Returns:\n            MyMonad[B]: Result of applying the wrapped function\n        \"\"\"\n        wrapped_funcs = cast(MyMonad[Callable[[A], B]], wrapped_funcs) # This line is needed because python doesn't support higher kinded types, also it's safe because we know an instance of `Applicative['MyMonad', Callable[[A], B]]` is an instance of `MyMonad[Callable[[A], B]]`\n        return MyMonad(wrapped_funcs._value(self._value))\n```\n\n**Step 5: Implement the `bind` Method**\n\n```python\n    def bind[B](\n        self,\n        f: Callable[[A], Monad['MyMonad', B]]\n    ) -\u003e 'MyMonad[B]':\n        \"\"\"\n        Chain a computation that returns a monad.\n        \n        This is the key method that defines monadic behavior.\n        Unlike fmap, the function f returns a monad, and bind\n        flattens the result to prevent nested monads.\n        \n        Args:\n            f: A function that takes a value and returns a monad\n            \n        Returns:\n            MyMonad[B]: The result of applying f and flattening\n        \"\"\"\n        # Apply the function to the value - it returns MyMonad[B]\n        # No need to wrap again, just return the result\n        f = cast(Callable[[A], MyMonad[B]]) # This line is needed because python doesn't support higher kinded types, also it's safe because we know and instance of `Monad['MyMonad', B]` is an instance of MyMonad[B]\n        return f(self._value)\n```\n\n**Step 6: Add Type Hints For Operators**\n\n```python\n    def __pow__[B](self, other: Applicative['MyMonad', Callable[[A], B]]) -\u003e 'MyMonad[B]':\n        \"\"\"Enable ** operator for applicative application.\"\"\"\n        return self.ap(other)\n    \n    def __or__[B](self, f: Callable[[A], Monad['MyMonad', B]]) -\u003e 'MyMonad[B]':\n        \"\"\"Enable | operator for monadic bind.\"\"\"\n        return self.bind(f)\n```\n\n**Key Differences from Functor and Applicative:**\n- **Functor**: Only maps functions over values (`fmap`) - transforms values in context\n- **Applicative**: Can apply wrapped functions to wrapped values (`ap`) - combines independent computations\n- **Monad**: Can chain dependent computations where each step depends on the previous result (`bind`) - enables sequential, dependent operations\n\n**Common Use Cases:**\n- **Error handling**: Chain operations that can fail, with automatic error propagation\n- **Optional values**: Chain operations on values that might not exist\n- **Asynchronous operations**: Chain async operations where each depends on the previous result\n- **State management**: Thread state through a sequence of computations\n- **Parsing**: Chain parsers where each parser depends on the previous result\n- **Database queries**: Chain queries where each query depends on the previous result\n- **I/O operations**: Chain I/O operations while maintaining purity\n\n\n## ds (Data Structures)\n\nThe `ds` module provides functional data structures that implement algebraic type classes (Functor, Applicative, Monad, Semigroup, Monoid). These data structures enable type-safe, composable functional programming patterns in Python.\n\n### Overview\n\nThe module includes:\n- **Maybe**: Optional values with type-safe null handling\n- **Result**: Error handling without exceptions\n- **ImmutableList**: Immutable list with monadic operations\n- **NonEmptyList**: List guaranteed to have at least one element\n- **IO**: Encapsulation of side effects\n- **MonoidMaybe**: Monoid wrapper for Maybe values\n\n### Maybe\n\nThe `Maybe` type represents computations that might fail or values that might be absent. It's an alternative to using `None` that forces explicit handling of the absence case.\n\n#### Constructors\n\n```python\nfrom katharos.ds import Maybe, Just, Nothing\n\n# Create a value that exists\nvalue = Just(42)\n\n# Create an absent value\nabsent = Nothing()\n\n# Using pure (returns Just)\nvalue = Maybe.pure(42)\n```\n\n#### Basic Operations\n\n**Functor - Transform values:**\n```python\n# Map a function over a Just value\nresult = Just(5).fmap(lambda x: x * 2)  # Just(10)\n\n# Map over Nothing returns Nothing\nresult = Nothing().fmap(lambda x: x * 2)  # Nothing()\n```\n\n**Applicative - Apply wrapped functions:**\n```python\n# Apply a wrapped function to a wrapped value\nvalue = Just(5)\nfunc = Just(lambda x: x * 2)\nresult = value.ap(func)  # Just(10)\n\n# Using ** operator\nresult = value ** func  # Just(10)\n\n# If either is Nothing, result is Nothing\nresult = Nothing() ** Just(lambda x: x * 2)  # Nothing()\n```\n\n**Monad - Chain dependent computations:**\n```python\ndef safe_divide(x: float) -\u003e Maybe[float]:\n    if x == 0:\n        return Nothing()\n    return Just(10.0 / x)\n\ndef safe_sqrt(x: float) -\u003e Maybe[float]:\n    if x \u003c 0:\n        return Nothing()\n    return Just(x ** 0.5)\n\n# Chain operations with bind\nresult = Just(2).bind(safe_divide).bind(safe_sqrt)  # Just(2.236...)\n\n# Using | operator for chaining\nresult = Just(4) | safe_divide | safe_sqrt  # Just(1.581...)\n\n# Failure propagates automatically\nresult = Just(0) | safe_divide | safe_sqrt  # Nothing()\n```\n\n#### Pattern Matching\n\n```python\nmatch maybe_value:\n    case Just(value=x):\n        print(f\"Got value: {x}\")\n    case Nothing():\n        print(\"No value\")\n```\n\n#### Use Cases\n\n- **Null safety**: Replace `None` with explicit Maybe types\n- **Optional configuration**: Handle missing config values\n- **Database queries**: Represent records that might not exist\n- **Parsing**: Handle values that might fail to parse\n- **API responses**: Handle optional fields in responses\n\n### MonoidMaybe\n\nA Monoid wrapper for `Maybe` values where the inner type is a Semigroup. Enables combining Maybe values with a sensible identity element.\n\n```python\nfrom katharos.ds import MonoidMaybe, Just, Nothing\n\n# Create MonoidMaybe instances (assuming inner type is Semigroup)\nm1 = MonoidMaybe(Just(value1))\nm2 = MonoidMaybe(Just(value2))\n\n# Combine using monoid operation\nresult = m1.op(m2)  # Combines inner values if both are Just\n\n# Identity element\nidentity = MonoidMaybe.identity()  # MonoidMaybe(Nothing())\n\n# Nothing acts as identity\nMonoidMaybe(Nothing()).op(m1) == m1  # True\nm1.op(MonoidMaybe(Nothing())) == m1  # True\n```\n\n### Result\n\nThe `Result` type represents computations that can either succeed with a value (`Success`) or fail with an exception (`Failure`). It provides railway-oriented programming for error handling.\n\n#### Constructors\n\n```python\nfrom katharos.ds import Result, Success, Failure\n\n# Create a successful result\nsuccess = Success(42)\n\n# Create a failed result\nfailure = Failure(ValueError(\"Something went wrong\"))\n\n# Using pure (returns Success)\nsuccess = Result.pure(42)\n```\n\n#### Basic Operations\n\n**Functor - Transform successful values:**\n```python\n# Map over Success\nresult = Success(5).fmap(lambda x: x * 2)  # Success(10)\n\n# Map over Failure returns the same Failure\nresult = Failure(ValueError(\"error\")).fmap(lambda x: x * 2)  # Failure(ValueError(\"error\"))\n```\n\n**Applicative - Apply wrapped functions:**\n```python\n# Apply a wrapped function\nvalue = Success(5)\nfunc = Success(lambda x: x * 2)\nresult = value.ap(func)  # Success(10)\n\n# Using ** operator\nresult = value ** func  # Success(10)\n\n# Failure propagates\nresult = Failure(ValueError(\"error\")) ** func  # Failure(ValueError(\"error\"))\n```\n\n**Monad - Chain operations that can fail:**\n```python\ndef parse_int(s: str) -\u003e Result[int, ValueError]:\n    try:\n        return Success(int(s))\n    except ValueError as e:\n        return Failure(e)\n\ndef divide_by_two(x: int) -\u003e Result[float, Exception]:\n    return Success(x / 2)\n\n# Chain operations with bind\nresult = Success(\"42\").bind(parse_int).bind(divide_by_two)  # Success(21.0)\n\n# Using | operator\nresult = Success(\"42\") | parse_int | divide_by_two  # Success(21.0)\n\n# Error propagates through the chain\nresult = Success(\"not_a_number\") | parse_int | divide_by_two  # Failure(ValueError(...))\n```\n\n#### Pattern Matching\n\n```python\nmatch result:\n    case Success(value=x):\n        print(f\"Success: {x}\")\n    case Failure(error=e):\n        print(f\"Error: {e}\")\n```\n\n#### Use Cases\n\n- **Error handling**: Replace try/except with functional error handling\n- **Validation**: Chain validation steps with automatic error propagation\n- **File I/O**: Handle file operations that can fail\n- **Network requests**: Handle API calls that can fail\n- **Data transformation pipelines**: Chain transformations with error handling\n\n### ImmutableList\n\nAn immutable list implementation that supports Functor, Applicative, Monad, and Monoid operations. Provides a functional alternative to Python's mutable lists.\n\n#### Constructors\n\n```python\nfrom katharos.ds import ImmutableList\n\n# Create from a list\nlst = ImmutableList([1, 2, 3, 4, 5])\n\n# Create a singleton list\nsingleton = ImmutableList.pure(42)  # ImmutableList([42])\n\n# Empty list (identity element)\nempty = ImmutableList.identity()  # ImmutableList([])\n```\n\n#### Basic Operations\n\n**Functor - Transform elements:**\n```python\n# Map a function over all elements\nnumbers = ImmutableList([1, 2, 3, 4])\ndoubled = numbers.fmap(lambda x: x * 2)  # ImmutableList([2, 4, 6, 8])\n\n# Type transformations\nstrings = numbers.fmap(str)  # ImmutableList(['1', '2', '3', '4'])\n```\n\n**Applicative - Cartesian product of functions and values:**\n```python\n# Apply multiple functions to multiple values\nvalues = ImmutableList([1, 2, 3])\nfuncs = ImmutableList([lambda x: x * 2, lambda x: x + 10])\n\nresult = values.ap(funcs)\n# ImmutableList([2, 4, 6, 11, 12, 13])\n\n# Using ** operator\nresult = values ** funcs\n```\n\n**Monad - Flatten nested lists:**\n```python\n# bind (flatMap) flattens the result\ndef duplicate(x: int) -\u003e ImmutableList[int]:\n    return ImmutableList([x, x])\n\nnumbers = ImmutableList([1, 2, 3])\nresult = numbers.bind(duplicate)  # ImmutableList([1, 1, 2, 2, 3, 3])\n\n# Using | operator\nresult = numbers | duplicate\n\n# Generate combinations\ndef pair_with_next(x: int) -\u003e ImmutableList[tuple[int, int]]:\n    return ImmutableList([(x, x+1), (x, x+2)])\n\nresult = ImmutableList([1, 2]) | pair_with_next\n# ImmutableList([(1, 2), (1, 3), (2, 3), (2, 4)])\n```\n\n**Monoid - Concatenation:**\n```python\n# Concatenate lists\nlist1 = ImmutableList([1, 2, 3])\nlist2 = ImmutableList([4, 5, 6])\n\n# Using op method\nresult = list1.op(list2)  # ImmutableList([1, 2, 3, 4, 5, 6])\n\n# Using @ operator (semigroup operation)\nresult = list1 @ list2  # ImmutableList([1, 2, 3, 4, 5, 6])\n\n# Using + operator\nresult = list1 + list2  # ImmutableList([1, 2, 3, 4, 5, 6])\n\n# Identity element\nempty = ImmutableList.identity()\nlist1.op(empty) == list1  # True\n```\n\n#### Sequence Operations\n\n```python\n# Length\nlen(ImmutableList([1, 2, 3]))  # 3\n\n# Indexing\nlst = ImmutableList([10, 20, 30])\nlst[0]  # 10\nlst[1]  # 20\n\n# Membership\n3 in ImmutableList([1, 2, 3])  # True\n\n# Iteration\nfor x in ImmutableList([1, 2, 3]):\n    print(x)\n\n# Convert to list\nlist(ImmutableList([1, 2, 3]))  # [1, 2, 3]\n\n# Equality and hashing\nImmutableList([1, 2]) == ImmutableList([1, 2])  # True\nhash(ImmutableList([1, 2]))  # Can be used in sets/dicts\n```\n\n#### Use Cases\n\n- **Immutable data structures**: Thread-safe data sharing\n- **Functional pipelines**: Chain transformations on collections\n- **List comprehensions**: Functional alternative with explicit types\n- **Combinations and permutations**: Generate combinations using bind\n- **Data processing**: Transform collections functionally\n\n### NonEmptyList\n\nA list guaranteed to contain at least one element. Useful when you need to ensure a collection is never empty.\n\n#### Constructors\n\n```python\nfrom katharos.ds import NonEmptyList\n\n# Create with head and tail\nnel = NonEmptyList(head=1, tail=[2, 3, 4])\n\n# Create singleton\nsingleton = NonEmptyList.pure(42)  # NonEmptyList(head=42, tail=[])\n```\n\n#### Properties\n\n```python\nnel = NonEmptyList(head=1, tail=[2, 3, 4])\n\n# Access head (first element)\nnel.head  # 1\n\n# Access tail (remaining elements)\nnel.tail  # [2, 3, 4]\n```\n\n#### Operations\n\n**Functor:**\n```python\nnel = NonEmptyList(head=1, tail=[2, 3])\ndoubled = nel.fmap(lambda x: x * 2)  # NonEmptyList(head=2, tail=[4, 6])\n```\n\n**Applicative:**\n```python\nvalues = NonEmptyList(head=1, tail=[2])\nfuncs = NonEmptyList(head=lambda x: x * 2, tail=[lambda x: x + 10])\nresult = values.ap(funcs)  # NonEmptyList with all combinations\n```\n\n**Monad:**\n```python\ndef duplicate(x: int) -\u003e NonEmptyList[int]:\n    return NonEmptyList(head=x, tail=[x])\n\nnel = NonEmptyList(head=1, tail=[2])\nresult = nel.bind(duplicate)  # NonEmptyList(head=1, tail=[1, 2, 2])\n```\n\n**Semigroup (Concatenation):**\n```python\nnel1 = NonEmptyList(head=1, tail=[2])\nnel2 = NonEmptyList(head=3, tail=[4])\n\n# Using op method\nresult = nel1.op(nel2)  # NonEmptyList(head=1, tail=[2, 3, 4])\n\n# Using + operator\nresult = nel1 + nel2  # NonEmptyList(head=1, tail=[2, 3, 4])\n```\n\n#### Use Cases\n\n- **Aggregations**: Ensure at least one value for operations like max/min\n- **Configuration**: Require at least one option\n- **User input**: Validate non-empty collections\n- **Graph algorithms**: Represent paths that must have at least one node\n- **Fold operations**: Safe folding without needing initial value\n\n### IO\n\nThe `IO` type encapsulates side effects, allowing you to describe I/O operations without immediately executing them. This maintains referential transparency in functional code.\n\n#### Constructors\n\n```python\nfrom katharos.ds import IO\n\n# Create an IO action with a value\nio = IO(42)\n\n# Using pure\nio = IO.pure(42)\n```\n\n#### Basic Operations\n\n**Functor - Transform the value:**\n```python\nio = IO(5)\ndoubled = io.fmap(lambda x: x * 2)  # IO(10)\n\n# Value is not executed until you call execute()\ndoubled.value  # 10\n```\n\n**Applicative - Apply wrapped functions:**\n```python\nvalue = IO(5)\nfunc = IO(lambda x: x * 2)\n\nresult = value.ap(func)  # IO(10)\n\n# Using ** operator\nresult = value ** func  # IO(10)\n```\n\n**Monad - Chain I/O operations:**\n```python\ndef read_config(path: str) -\u003e IO[dict]:\n    # In practice, this would read from file\n    return IO({\"setting\": \"value\"})\n\ndef process_config(config: dict) -\u003e IO[str]:\n    return IO(config.get(\"setting\", \"default\"))\n\n# Chain operations\nresult = IO(\"config.json\").bind(read_config).bind(process_config)\n\n# Using | operator\nresult = IO(\"config.json\") | read_config | process_config\n```\n\n**Sequencing - Combine side effects:**\n```python\nfrom katharos.ds.side_effect import FunctionWithSideEffect\n\ndef print_action():\n    print(\"Hello\")\n\ndef write_action():\n    print(\"World\")\n\nio1 = IO(None, FunctionWithSideEffect(f=print_action))\nio2 = IO(None, FunctionWithSideEffect(f=write_action))\n\n# Sequence operations (\u003e\u003e operator)\ncombined = io1 \u003e\u003e io2\n\n# Execute both side effects in order\ncombined.execute()  # Prints: Hello\\nWorld\n```\n\n#### Execution\n\n```python\n# Create an IO action\nio = IO(42)\n\n# Execute the side effects (if any)\nio.execute()\n\n# Access the value\nio.value  # 42\n```\n\n#### Use Cases\n\n- **File I/O**: Describe file operations without executing them\n- **Console I/O**: Describe print/input operations\n- **Database operations**: Describe queries without executing\n- **Network requests**: Describe HTTP calls without making them\n- **Testing**: Mock I/O operations by replacing IO actions\n- **Composition**: Build complex I/O operations from simple ones\n\n### Common Patterns\n\n#### Error Handling with Result\n\n```python\nfrom katharos.ds import Result, Success, Failure\n\ndef validate_age(age: int) -\u003e Result[int, ValueError]:\n    if age \u003c 0:\n        return Failure(ValueError(\"Age cannot be negative\"))\n    if age \u003e 150:\n        return Failure(ValueError(\"Age too high\"))\n    return Success(age)\n\ndef calculate_birth_year(age: int) -\u003e Result[int, Exception]:\n    from datetime import datetime\n    return Success(datetime.now().year - age)\n\n# Chain validations\nresult = Success(25) | validate_age | calculate_birth_year\nmatch result:\n    case Success(value=year):\n        print(f\"Born in {year}\")\n    case Failure(error=e):\n        print(f\"Error: {e}\")\n```\n\n#### Optional Values with Maybe\n\n```python\nfrom katharos.ds import Maybe, Just, Nothing\n\ndef get_user(user_id: int) -\u003e Maybe[dict]:\n    # Simulate database lookup\n    users = {1: {\"name\": \"Alice\"}, 2: {\"name\": \"Bob\"}}\n    if user_id in users:\n        return Just(users[user_id])\n    return Nothing()\n\ndef get_name(user: dict) -\u003e Maybe[str]:\n    return Just(user.get(\"name\")) if \"name\" in user else Nothing()\n\n# Chain operations\nresult = Just(1) | get_user | get_name\nmatch result:\n    case Just(value=name):\n        print(f\"User name: {name}\")\n    case Nothing():\n        print(\"User not found\")\n```\n\n#### List Comprehensions with ImmutableList\n\n```python\nfrom katharos.ds import ImmutableList\n\n# Traditional list comprehension\n# result = [x * y for x in [1, 2, 3] for y in [10, 20]]\n\n# Functional equivalent using bind\nnumbers = ImmutableList([1, 2, 3])\nmultipliers = ImmutableList([10, 20])\n\nresult = numbers.bind(\n    lambda x: multipliers.fmap(lambda y: x * y)\n)\n# ImmutableList([10, 20, 20, 40, 30, 60])\n```\n\n#### Combining Multiple Maybe Values\n\n```python\nfrom katharos.ds import Maybe, Just, Nothing\n\ndef add(x: int) -\u003e Callable[[int], int]:\n    return lambda y: x + y\n\n# Applicative style - combine independent computations\nmaybe_x = Just(5)\nmaybe_y = Just(10)\nmaybe_func = Just(add(5))\n\nresult = maybe_y.ap(maybe_func)  # Just(15)\n\n# If any is Nothing, result is Nothing\nresult = Nothing().ap(Just(lambda x: x + 1))  # Nothing()\n```\n\n### Operator Summary\n\n| Operator | Type Class | Method | Description |\n|----------|-----------|---------|-------------|\n| `fmap(f)` | Functor | - | Map function over wrapped value |\n| `**` | Applicative | `ap` | Apply wrapped function to wrapped value |\n| `\\|` | Monad | `bind` | Chain dependent computations |\n| `\u003e\u003e` | Monad | `sequence` | Sequence actions, discard first result |\n| `@` | Semigroup | `op` | Combine two values |\n\n### Type Safety\n\nAll data structures are fully typed with Python's type system:\n\n```python\nfrom katharos.ds import Maybe, Just, ImmutableList\n\n# Type inference works correctly\nnumbers: Maybe[int] = Just(42)\nstrings: ImmutableList[str] = ImmutableList([\"a\", \"b\", \"c\"])\n\n# Type transformations are tracked\nresult: Maybe[str] = numbers.fmap(str)  # Maybe[int] -\u003e Maybe[str]\n```\n\n### Algebraic Laws\n\nAll data structures satisfy their respective algebraic laws:\n\n**Functor Laws:**\n1. Identity: `x.fmap(id) == x`\n2. Composition: `x.fmap(f).fmap(g) == x.fmap(lambda x: g(f(x)))`\n\n**Applicative Laws:**\n1. Identity: `v.ap(pure(id)) == v`\n2. Homomorphism: `pure(x).ap(pure(f)) == pure(f(x))`\n3. Interchange: `pure(y).ap(u) == u.ap(pure(lambda f: f(y)))`\n\n**Monad Laws:**\n1. Left identity: `pure(x).bind(f) == f(x)`\n2. Right identity: `m.bind(pure) == m`\n3. Associativity: `m.bind(f).bind(g) == m.bind(lambda x: f(x).bind(g))`\n\n**Monoid Laws:**\n1. Left identity: `identity.op(x) == x`\n2. Right identity: `x.op(identity) == x`\n3. Associativity: `(x.op(y)).op(z) == x.op(y.op(z))`\n\nThese laws ensure predictable, composable behavior across all operations.\n\n## functools\n\nThe `functools` module provides utility functions for functional programming, including function composition, identity, and fold operations. All utilities are available through the `F` class as static methods.\n\n### Overview\n\nThe module provides:\n- **compose**: Function composition\n- **id**: Identity function\n- **foldl**: Left fold over iterables\n- **foldr**: Right fold over iterables\n- **sigma**: Combine semigroup elements\n\n### F Class\n\nAll utilities are accessed through the `F` class as static methods. No instantiation is required.\n\n```python\nfrom katharos.functools import F\n```\n\n### compose\n\nCompose two functions together, creating a new function that applies them in sequence.\n\n**Signature:**\n```python\nF.compose[A, B, C](f: Callable[[B], C]) -\u003e Callable[[Callable[[A], B]], Callable[[A], C]]\n```\n\n**Description:**\n\nFunction composition follows mathematical notation: `(f ∘ g)(x) = f(g(x))`. The `compose` function takes a function `f` and returns a function that takes another function `g`, producing a composed function that applies `g` first, then `f`.\n\n**Examples:**\n\n```python\nfrom katharos.functools import F\n\n# Basic composition\ndef add_one(x: int) -\u003e int:\n    return x + 1\n\ndef multiply_by_two(x: int) -\u003e int:\n    return x * 2\n\n# Compose: multiply_by_two(add_one(x))\ncomposed = F.compose(multiply_by_two)(add_one)\nresult = composed(3)  # (3 + 1) * 2 = 8\n\n# String operations\ndef to_upper(s: str) -\u003e str:\n    return s.upper()\n\ndef add_exclamation(s: str) -\u003e str:\n    return s + \"!\"\n\ncomposed = F.compose(add_exclamation)(to_upper)\nresult = composed(\"hello\")  # \"HELLO!\"\n\n# Type transformations\ndef int_to_str(x: int) -\u003e str:\n    return str(x)\n\ndef str_length(s: str) -\u003e int:\n    return len(s)\n\ncomposed = F.compose(str_length)(int_to_str)\nresult = composed(12345)  # 5\n```\n\n**Multiple Compositions:**\n\n```python\ndef add_one(x: int) -\u003e int:\n    return x + 1\n\ndef multiply_by_two(x: int) -\u003e int:\n    return x * 2\n\ndef subtract_three(x: int) -\u003e int:\n    return x - 3\n\n# Compose multiple functions\n# subtract_three(multiply_by_two(add_one(x)))\ncomposed = F.compose(subtract_three)(\n    F.compose(multiply_by_two)(add_one)\n)\nresult = composed(5)  # ((5 + 1) * 2) - 3 = 9\n```\n\n**Use Cases:**\n- **Pipeline construction**: Build data transformation pipelines\n- **Function reuse**: Combine existing functions without creating new ones\n- **Point-free style**: Write code without explicitly mentioning arguments\n- **Abstraction**: Create higher-level operations from simpler ones\n\n### id\n\nThe identity function returns its argument unchanged. Useful as a default or no-op function.\n\n**Signature:**\n```python\nF.id[A](x: A) -\u003e A\n```\n\n**Description:**\n\nThe identity function is the neutral element for function composition: `compose(f)(id) = f` and `compose(id)(f) = f`. It's commonly used in functional programming as a default function or to satisfy type requirements.\n\n**Examples:**\n\n```python\nfrom katharos.functools import F\n\n# Basic usage\nF.id(42)        # 42\nF.id(\"hello\")   # \"hello\"\nF.id([1, 2, 3]) # [1, 2, 3]\nF.id(None)      # None\n\n# Identity preserves object identity\nlst = [1, 2, 3]\nF.id(lst) is lst  # True\n\n# Used with fmap (from Functor)\nfrom katharos.ds import Just\n\nmaybe_value = Just(42)\nsame_value = maybe_value.fmap(F.id)  # Just(42)\n\n# Used as a default function\ndef process(value: int, transform: Callable[[int], int] = F.id) -\u003e int:\n    return transform(value)\n\nprocess(10)              # 10 (uses identity)\nprocess(10, lambda x: x * 2)  # 20 (uses custom function)\n```\n\n**Use Cases:**\n- **Default function parameter**: Provide a no-op default\n- **Testing functor laws**: Verify `fmap(id) = id`\n- **Placeholder**: Use where a function is required but no transformation is needed\n- **Function composition identity**: Neutral element in composition\n\n### foldl\n\nLeft fold (reduce) a function over an iterable, processing elements from left to right.\n\n**Signature:**\n```python\nF.foldl[A, B](f: Callable[[B, A], B], acc: B, xs: Iterable[A]) -\u003e B\n```\n\n**Description:**\n\nLeft fold processes elements from left to right, accumulating a result. The function `f` takes the accumulator as the first argument and the current element as the second. This is equivalent to Python's `functools.reduce` but with explicit initial value.\n\n**Process:** `foldl(f, acc, [x1, x2, x3]) = f(f(f(acc, x1), x2), x3)`\n\n**Examples:**\n\n```python\nfrom katharos.functools import F\n\n# Sum of numbers\nresult = F.foldl(lambda acc, x: acc + x, 0, [1, 2, 3, 4])\n# 0 + 1 = 1, 1 + 2 = 3, 3 + 3 = 6, 6 + 4 = 10\n# Result: 10\n\n# String concatenation\nresult = F.foldl(lambda acc, x: acc + x, \"\", [\"a\", \"b\", \"c\"])\n# \"\" + \"a\" = \"a\", \"a\" + \"b\" = \"ab\", \"ab\" + \"c\" = \"abc\"\n# Result: \"abc\"\n\n# Build a list\nresult = F.foldl(lambda acc, x: acc + [x], [], [1, 2, 3])\n# Result: [1, 2, 3]\n\n# Reverse a list\nresult = F.foldl(lambda acc, x: [x] + acc, [], [1, 2, 3])\n# [] + [1] = [1], [2, 1], [3, 2, 1]\n# Result: [3, 2, 1]\n\n# Product of numbers\nresult = F.foldl(lambda acc, x: acc * x, 1, [2, 3, 4])\n# Result: 24\n\n# Count elements\nresult = F.foldl(lambda acc, x: acc + 1, 0, [10, 20, 30])\n# Result: 3\n\n# Maximum value\nresult = F.foldl(lambda acc, x: max(acc, x), float('-inf'), [3, 7, 2, 9, 1])\n# Result: 9\n```\n\n**With Generators:**\n\n```python\n# Works with any iterable\nresult = F.foldl(lambda acc, x: acc + x, 0, (x for x in range(1, 5)))\n# Result: 10\n```\n\n**Use Cases:**\n- **Aggregation**: Sum, product, min, max operations\n- **List construction**: Build lists from iterables\n- **State accumulation**: Thread state through a sequence\n- **Custom reductions**: Any operation that combines elements sequentially\n\n### foldr\n\nRight fold (reduce) a function over an iterable, processing elements from right to left.\n\n**Signature:**\n```python\nF.foldr[A, B](f: Callable[[A, B], B], acc: B, xs: Iterable[A]) -\u003e B\n```\n\n**Description:**\n\nRight fold processes elements from right to left, accumulating a result. The function `f` takes the current element as the first argument and the accumulator as the second. This is useful for operations where order matters or for building right-associative structures.\n\n**Process:** `foldr(f, acc, [x1, x2, x3]) = f(x1, f(x2, f(x3, acc)))`\n\n**Examples:**\n\n```python\nfrom katharos.functools import F\n\n# Sum of numbers\nresult = F.foldr(lambda x, acc: x + acc, 0, [1, 2, 3, 4])\n# f(1, f(2, f(3, f(4, 0))))\n# Result: 10\n\n# String concatenation\nresult = F.foldr(lambda x, acc: x + acc, \"\", [\"a\", \"b\", \"c\"])\n# f(\"a\", f(\"b\", f(\"c\", \"\")))\n# \"a\" + (\"b\" + (\"c\" + \"\")) = \"abc\"\n# Result: \"abc\"\n\n# Build a list (preserves order)\nresult = F.foldr(lambda x, acc: [x] + acc, [], [1, 2, 3])\n# Result: [1, 2, 3]\n\n# Subtraction (demonstrates right-associativity)\nresult = F.foldr(lambda x, acc: x - acc, 0, [1, 2, 3])\n# 1 - (2 - (3 - 0)) = 1 - (2 - 3) = 1 - (-1) = 2\n# Result: 2\n\n# Compare with foldl for non-associative operations\nfoldl_result = F.foldl(lambda acc, x: acc - x, 0, [1, 2, 3])\n# (0 - 1) - 2 - 3 = -6\n# Result: -6 (different from foldr!)\n\n# Product of numbers\nresult = F.foldr(lambda x, acc: x * acc, 1, [2, 3, 4])\n# Result: 24\n```\n\n**Use Cases:**\n- **Right-associative operations**: Operations where right-to-left matters\n- **List construction**: Build lists while preserving order\n- **Tree building**: Construct right-leaning trees\n- **Lazy evaluation**: Can short-circuit in lazy languages (not applicable in Python)\n\n### Fold Comparison\n\n**Associative Operations:**\n\nFor associative operations (like addition, multiplication), `foldl` and `foldr` produce the same result:\n\n```python\n# Addition is associative: (a + b) + c = a + (b + c)\nF.foldl(lambda acc, x: acc + x, 0, [1, 2, 3, 4])  # 10\nF.foldr(lambda x, acc: x + acc, 0, [1, 2, 3, 4])  # 10\n```\n\n**Non-Associative Operations:**\n\nFor non-associative operations (like subtraction), they produce different results:\n\n```python\n# Subtraction is not associative\nF.foldl(lambda acc, x: acc - x, 0, [1, 2, 3])  # -6\nF.foldr(lambda x, acc: x - acc, 0, [1, 2, 3])  # 2\n```\n\n**Performance Considerations:**\n\n- `foldl` is generally more efficient in strict languages like Python\n- `foldr` requires converting the iterable to a list and reversing it\n- For large iterables with associative operations, prefer `foldl`\n\n### sigma\n\nCombine all elements of a non-empty list using the semigroup operation (`@` operator).\n\n**Signature:**\n```python\nF.sigma[A: Semigroup](xs: NonEmptyList[A]) -\u003e A\n```\n\n**Description:**\n\nThe `sigma` function (Σ) combines all elements in a non-empty list using their semigroup operation. This is a specialized fold that uses the `@` operator (matmul) which is overloaded for semigroup types.\n\n**Examples:**\n\n```python\nfrom katharos.functools import F\nfrom katharos.ds import NonEmptyList, ImmutableList\n\n# Combine ImmutableLists (which are Semigroups)\nlists = NonEmptyList(\n    head=ImmutableList([1, 2]),\n    tail=[ImmutableList([3, 4]), ImmutableList([5, 6])]\n)\nresult = F.sigma(lists)\n# ImmutableList([1, 2]) @ ImmutableList([3, 4]) @ ImmutableList([5, 6])\n# Result: ImmutableList([1, 2, 3, 4, 5, 6])\n\n# Combine NonEmptyLists\nnels = NonEmptyList(\n    head=NonEmptyList(head=1, tail=[2]),\n    tail=[NonEmptyList(head=3, tail=[4])]\n)\nresult = F.sigma(nels)\n# Result: NonEmptyList(head=1, tail=[2, 3, 4])\n```\n\n**Custom Semigroups:**\n\n```python\nfrom katharos.algebra import Semigroup\n\nclass Sum(Semigroup[\"Sum\"]):\n    def __init__(self, value: int):\n        self.value = value\n    \n    def op(self, other: \"Sum\") -\u003e \"Sum\":\n        return Sum(self.value + other.value)\n\n# Combine Sum instances\nsums = NonEmptyList(\n    head=Sum(1),\n    tail=[Sum(2), Sum(3), Sum(4)]\n)\nresult = F.sigma(sums)\n# Result: Sum(10)\n```\n\n**Use Cases:**\n- **Concatenation**: Combine multiple lists or strings\n- **Aggregation**: Sum or combine custom semigroup types\n- **Monoid operations**: Combine elements with associative operations\n- **Data merging**: Merge multiple data structures\n\n### Common Patterns\n\n#### Building Pipelines with Compose\n\n```python\nfrom katharos.functools import F\n\n# Define transformations\ndef parse_int(s: str) -\u003e int:\n    return int(s)\n\ndef double(x: int) -\u003e int:\n    return x * 2\n\ndef to_string(x: int) -\u003e str:\n    return f\"Result: {x}\"\n\n# Build pipeline\npipeline = F.compose(to_string)(F.compose(double)(parse_int))\n\n# Use pipeline\nresult = pipeline(\"21\")  # \"Result: 42\"\n```\n\n#### Implementing Map with Fold\n\n```python\nfrom katharos.functools import F\n\ndef map_with_foldl(f, xs):\n    return F.foldl(lambda acc, x: acc + [f(x)], [], xs)\n\nresult = map_with_foldl(lambda x: x * 2, [1, 2, 3, 4])\n# Result: [2, 4, 6, 8]\n```\n\n#### Implementing Filter with Fold\n\n```python\nfrom katharos.functools import F\n\ndef filter_with_foldl(predicate, xs):\n    return F.foldl(\n        lambda acc, x: acc + [x] if predicate(x) else acc,\n        [],\n        xs\n    )\n\nresult = filter_with_foldl(lambda x: x % 2 == 0, [1, 2, 3, 4, 5, 6])\n# Result: [2, 4, 6]\n```\n\n#### Counting with Fold\n\n```python\nfrom katharos.functools import F\n\ndef count_if(predicate, xs):\n    return F.foldl(\n        lambda acc, x: acc + 1 if predicate(x) else acc,\n        0,\n        xs\n    )\n\nresult = count_if(lambda x: x \u003e 5, [1, 3, 6, 8, 2, 9])\n# Result: 3\n```\n\n#### Grouping with Fold\n\n```python\nfrom katharos.functools import F\n\ndef group_by(key_func, xs):\n    def add_to_group(acc, x):\n        key = key_func(x)\n        if key not in acc:\n            acc[key] = []\n        acc[key].append(x)\n        return acc\n    \n    return F.foldl(add_to_group, {}, xs)\n\nresult = group_by(lambda x: x % 2, [1, 2, 3, 4, 5, 6])\n# Result: {1: [1, 3, 5], 0: [2, 4, 6]}\n```\n\n#### Flattening Lists with Fold\n\n```python\nfrom katharos.functools import F\n\ndef flatten(nested_list):\n    return F.foldl(lambda acc, x: acc + x, [], nested_list)\n\nresult = flatten([[1, 2], [3, 4], [5, 6]])\n# Result: [1, 2, 3, 4, 5, 6]\n```\n\n### Integration with Other Modules\n\n#### With Maybe\n\n```python\nfrom katharos.functools import F\nfrom katharos.ds import Just, Nothing\n\n# Use id with Maybe\nJust(42).fmap(F.id)  # Just(42)\n\n# Use compose with Maybe operations\ndef safe_divide(x: float):\n    return Nothing() if x == 0 else Just(10.0 / x)\n\ndef safe_sqrt(x: float):\n    return Nothing() if x \u003c 0 else Just(x ** 0.5)\n\n# Compose doesn't work directly with monadic functions,\n# but you can use bind (|) operator instead\nresult = Just(2) | safe_divide | safe_sqrt\n```\n\n#### With ImmutableList\n\n```python\nfrom katharos.functools import F\nfrom katharos.ds import ImmutableList\n\n# Use compose with fmap\nadd_one = lambda x: x + 1\ndouble = lambda x: x * 2\n\ntransform = F.compose(double)(add_one)\nresult = ImmutableList([1, 2, 3]).fmap(transform)\n# ImmutableList([4, 6, 8])\n\n# Use foldl to sum list elements\nnumbers = ImmutableList([1, 2, 3, 4, 5])\ntotal = F.foldl(lambda acc, x: acc + x, 0, numbers)\n# 15\n```\n\n#### With Result\n\n```python\nfrom katharos.functools import F\nfrom katharos.ds import Success, Failure\n\n# Use id with Result\nSuccess(42).fmap(F.id)  # Success(42)\n\n# Use compose for transformations\nparse = lambda s: int(s)\ndouble = lambda x: x * 2\n\ntransform = F.compose(double)(parse)\nresult = Success(\"21\").fmap(transform)\n# Success(42)\n```\n\n### Best Practices\n\n**1. Use compose for pure functions:**\n```python\n# Good: Compose pure functions\ntransform = F.compose(f)(g)\n\n# Avoid: Don't compose functions with side effects\n# Bad example (side effects in composition)\n```\n\n**2. Choose the right fold:**\n```python\n# Use foldl for efficiency (left-to-right)\nF.foldl(lambda acc, x: acc + x, 0, large_list)\n\n# Use foldr when order matters (right-to-left)\nF.foldr(lambda x, acc: [x] + acc, [], items)\n```\n\n**3. Leverage id for clarity:**\n```python\n# Good: Use id as a clear no-op\ndef process(value, transform=F.id):\n    return transform(value)\n\n# Avoid: Don't create custom identity functions\n# Bad: lambda x: x  # Use F.id instead\n```\n\n**4. Type annotations with compose:**\n```python\n# Good: Clear type annotations\ndef f(x: int) -\u003e str:\n    return str(x)\n\ndef g(x: float) -\u003e int:\n    return int(x)\n\ncomposed: Callable[[float], str] = F.compose(f)(g)\n```\n\n### Summary\n\nThe `functools` module provides essential functional programming utilities:\n\n- **F.compose**: Combine functions into pipelines\n- **F.id**: Identity function for defaults and testing\n- **F.foldl**: Efficient left-to-right reduction\n- **F.foldr**: Right-to-left reduction for order-sensitive operations\n- **F.sigma**: Combine semigroup elements\n\nThese utilities enable point-free style programming, function composition, and powerful data transformations while maintaining type safety and functional purity.\n\n## License\n\nMIT License","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkamalfarahani%2Fkatharos","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkamalfarahani%2Fkatharos","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkamalfarahani%2Fkatharos/lists"}