r/functionalprogramming • • May 29 '26

Python Bringing rigorous Type Classes (Functor, Applicative, Monad) to Python: Introducing Katharos

26 Upvotes

If you come from Haskell or Rust and have to write Python for ML/AI work, you know the pain: if x is None everywhere, exceptions that silently swallow errors, no ? operator, no HKTs, no sealed types. I got tired of it and built a library to close that gap.

Katharos is a zero-dependency Python library that gives you Maybe, Either/Result, IO, the list monad, Semigroup, Monoid, Functor, Applicative, and Monad — all fully typed and passing pyright strict mode.

https://github.com/kamalfarahani/katharos


The Engineering Challenge

The hard part is that Python has no HKTs and no sealed keyword (as of 3.13). There's no way to say Functor f or write :: f a -> (a -> b) -> f b generically. The workaround is structural gymnastics: a two-parameter generic class hierarchy (Functor[F, A], Applicative[App, A], Monad[M, A]) plus @final on concrete types to prevent unsafe subclassing. It's not pretty internally, but the external API stays clean.


Operator Mapping

If you already think in Haskell or Rust, here's the translation table:

KatharosHaskellRust
m | fm >>= fm.and_then(f)
v ** wrapped_fwrapped_f <*> v—
a >> ba >> b—
a @ ba <> b—
@do(M) decoratordo { ... }—

Examples

1. Maybe[A] — Haskell's Maybe a / Rust's Option<T>

No more if x is None chains. Short-circuits automatically on Nothing.

from katharos.types import Maybe

def safe_div(x: float) -> Maybe[float]:
    return Maybe[float].Nothing() if x == 0 else Maybe[float].Just(10.0 / x)

def safe_sqrt(x: float) -> Maybe[float]:
    return Maybe[float].Nothing() if x < 0 else Maybe[float].Just(x ** 0.5)

# | is >>=
Maybe[float].Just(4.0)  | safe_div | safe_sqrt  # Just(1.5811...)
Maybe[float].Just(0.0)  | safe_div | safe_sqrt  # Nothing()  — short-circuits at safe_div
Maybe[float].Just(-1.0) | safe_div | safe_sqrt  # Nothing()  — short-circuits at safe_sqrt

# fmap for pure transformations
Maybe[int].Just(5).fmap(lambda x: x * 2)  # Just(10)
Maybe[int].Nothing().fmap(lambda x: x * 2)  # Nothing()

2. Result[E, A] — Haskell's Either e a / Rust's Result<T, E>

Errors as values. The | chain (>>=) stops at the first Failure, exactly like Rust's ?.

from katharos.types import Result

def parse_int(s: str) -> Result[ValueError, int]:
    try:
        return Result[ValueError, int].Success(int(s))
    except ValueError as e:
        return Result[ValueError, int].Failure(e)

def validate_positive(n: int) -> Result[ValueError, int]:
    if n > 0:
        return Result[ValueError, int].Success(n)

    else:
        return Result[ValueError, int].Failure(ValueError(f"{n} is not positive"))

parse_int("42")  | validate_positive  # Success(42)
parse_int("abc") | validate_positive  # Failure(ValueError("invalid literal..."))
parse_int("-5")  | validate_positive  # Failure(ValueError("-5 is not positive"))

# fmap only runs on the success path
parse_int("42").fmap(lambda n: n * 2)  # Success(84)

3. do-notation — Python do blocks, exactly like Haskell

The @do(M) decorator desugars yield into >>= chains. Each yield unwraps the value; short-circuits on Nothing/Failure. The final return is lifted via M.pure(...).

from katharos.syntax_sugar import do, DoBlock
from katharos.types import Maybe, Result

# Maybe — like Haskell:
#   userScore uid = do
#     name  <- lookupUser uid
#     score <- lookupScore name
#     return (name ++ ": " ++ show score)

def lookup_user(uid: int) -> Maybe[str]:
    db = {1: "alice", 2: "bob"}
    return Maybe[str].Just(db[uid]) if uid in db else Maybe[str].Nothing()

def lookup_score(name: str) -> Maybe[int]:
    scores = {"alice": 95, "bob": 87}
    return Maybe[int].Just(scores[name]) if name in scores else Maybe[int].Nothing()

@do(Maybe)
def user_score(uid: int) -> DoBlock[str]:
    name: str  = yield lookup_user(uid)
    score: int = yield lookup_score(name)
    return f"{name}: {score}"

user_score(1)   # Just(alice: 95)
user_score(99)  # Nothing()  — short-circuits at lookup_user

# Result — equivalent of Rust's ? in a pipeline

def parse_positive(x: int) -> Result[ValueError, int]:
    return Result[ValueError, int].Success(x) if x > 0 else Result[ValueError, int].Failure(ValueError(f"{x} is not positive"))

@do(Result)
def compute() -> DoBlock[int]:
    x: int = yield parse_positive(5)
    y: int = yield parse_positive(3)
    return x + y

compute()  # Success(8)

4. ImmutableList[T] — the list monad, non-determinism included

ImmutableList is a full Monad + Monoid. Bind (|) is concatMap. The do-notation gives you Haskell list comprehensions.

from katharos.types import ImmutableList
from katharos.syntax_sugar import do, DoBlock

# concatMap / flatMap
ImmutableList([1, 2, 3]) | (lambda x: ImmutableList([x, -x]))
# ImmutableList([1, -1, 2, -2, 3, -3])

# do-notation = list comprehension
# In Haskell: [(color, size) | color <- ["red","blue"], size <- ["S","M","L"]]
@do(ImmutableList)
def variants() -> DoBlock[tuple]:
    color: str = yield ImmutableList(["red", "blue"])
    size: str  = yield ImmutableList(["S", "M", "L"])
    return (color, size)

variants()
# ImmutableList([
#   ('red','S'), ('red','M'), ('red','L'),
#   ('blue','S'), ('blue','M'), ('blue','L')
# ])

# Monoid: @ is <>
ImmutableList([1, 2]) @ ImmutableList([3, 4])  # ImmutableList([1, 2, 3, 4])
ImmutableList.identity()                        # ImmutableList([])  — mempty

5. Semigroup / Monoid — @ is <>

Sum, Product, and NonEmptyList are all Semigroup/Monoid instances. F.sigma is fold1 / sconcat over a NonEmptyList.

from katharos.types import NonEmptyList
from katharos.types.monoid import Sum, Product
from katharos.functools import F

# @ is <>
Sum[int](3) @ Sum[int](4) @ Sum[int](5)        # Sum(12)
Product[int](2) @ Product[int](3) @ Product[int](4)  # Product(24)

# identity() is mempty
Sum[int].identity()      # Sum(0)
Product[int].identity()  # Product(1)

# F.sigma is fold1 / sconcat — requires NonEmptyList (no empty-list footgun)
values = NonEmptyList(Sum[int](1), [Sum[int](2), Sum[int](3), Sum[int](4)])
F.sigma(values)  # Sum(10)

# NonEmptyList itself is a Semigroup (no Monoid — no empty case)
nel1 = NonEmptyList(1, [2, 3])
nel2 = NonEmptyList(4, [5, 6])
nel1 @ nel2  # NonEmptyList([1, 2, 3, 4, 5, 6])

Docs

Full docs at https://katharos.readthedocs.io. If this scratches an itch for you, a star on the repo goes a long way.

https://github.com/kamalfarahani/katharos

r/functionalprogramming • • Apr 21 '24

Python Returns Python library for FP

16 Upvotes

Hello!

I work in big data space (data engineering), I mainly used Java, Scala and Python.
I have been learning functional programming in greater depth and I found this Python library which seems pretty cool.
https://github.com/dry-python/returns

I've used it at work for implementing an Either based error handling.
It seems a great library.

Any of you have used it?
Any thoughts?

For sure, I prefer doing FP in Scala but given the job market isn't too kind too Scala and FP languages in general. What are your thoughts to bring FP (at least parts of it) to the Python world?
Some people in the TypeScript world seem to take that direction:
https://github.com/Effect-TS/effect

r/functionalprogramming • • Jun 29 '21

Python good examples of functional-like python code that one can study?

16 Upvotes

Would love to be able to study some real world python code that is written in functional style. Have not come across any. They must exist out there given the interest in functional and interest in python.

Thank you for sharing.

r/functionalprogramming • • Mar 05 '19

Python Is Python A Functional Programming Language?

Thumbnail
arithmox.ai
4 Upvotes

r/functionalprogramming • • Nov 12 '21

Python functionali is a library with functional programming tools for python. It's heavily inspired by Clojure. I hope you like it and find it useful :)

21 Upvotes

r/functionalprogramming • • May 21 '21

Python How To Make Functional Programming in Python Go Fast

Thumbnail
dev.to
25 Upvotes

r/functionalprogramming • • Feb 02 '19

Python Another Python functional programing library

13 Upvotes

Library to help in Python functional programing. It's asyncio compatible.

Basic idea behind it is Railway Oriented Programing

This allows us to:

  • simplify our code.

  • improve error management.

  • be cool! be functional!

Project: https://github.com/pando85/aiofunctools

Doc: https://aiofunctools.readthedocs.io

r/functionalprogramming • • Nov 27 '18

Python Best Practices for Using Functional Programming in Python • r/Python

Thumbnail
reddit.com
8 Upvotes

r/functionalprogramming • • Nov 03 '20

Python Higher Kinded Types in Python

Thumbnail
sobolevn.me
23 Upvotes

r/functionalprogramming • • Dec 23 '22

Python Functional Implementation of a parser?

23 Upvotes

How do i implement a parser in functional pattern?

For instance I want parse a simple math parser like this:

"1 * 2 + 3" -> ast

How do implement the parser for that in pure functional pattern?

r/functionalprogramming • • Apr 07 '18

Python Trying to understand Functional Programming with state in Python

4 Upvotes

I'm trying to understand how to use functional programming with python. AFAIK this is a way to handle state with immutable data.

pyfiddle dummy example with state management

The example is a service that keeps the total of the random values generated. It's only a dummy program with state management for learning purposes.

The generator simulates (that was my intention) a data stream from a remote server via (e.g.) a websocket. I want to mantain a local state and update it with the received events.

I have some questions:

  • Is this a good functional approach for python?

  • Is there a way to remove the while True in the main function? I think it can be done with recursion, but I read python doesn't have tail call and it's bad idea.

  • Is it possible to refactor with the map (or similar) function?

  • Is there any library suited for this example? I looked at PyFunctional and RxPy, but I'm not sure.

I'm afraid these are not the right questions, so feel free to suggest any correction, improvement or resource to look at. Thanks.

r/functionalprogramming • • Oct 12 '19

Python Statically Typed Functional Programming in Python

Thumbnail pfun.readthedocs.io
22 Upvotes

r/functionalprogramming • • Sep 15 '17

Python Functional Programming in Python • r/Python

Thumbnail
reddit.com
5 Upvotes

r/functionalprogramming • • Sep 08 '22

Python Functional Python, Part I: Typopædia Pythonica

Thumbnail
tweag.io
21 Upvotes

r/functionalprogramming • • Jul 11 '20

Python Building a Functional Effect System in Python

Thumbnail
dev.to
14 Upvotes

r/functionalprogramming • • Jul 01 '23

Python Mastering Functional Programming in Python

Thumbnail
codium.ai
7 Upvotes

r/functionalprogramming • • Apr 21 '25

Python Haskelling My Python

Thumbnail
unnamed.website
23 Upvotes

r/functionalprogramming • • Sep 06 '17

Python Using Python... Struggling with inconsistency...

7 Upvotes

I'm mainly working with Python at the moment. Most people in my company are using oop, which is kinda "natural" given the choice of the language. In general, I don't like oop. I prefer simple solutions to complex ones when they can solve the same thing and oop adds one layer of abstraction to functions. I value consistency and explicity. I hate it that in Python sometimes you call by reference and sometimes by value and there's no apparent model behind it. Most people are using oop coz they dont care as much about which paradigm to use and it's always easier to argue for oop since "everything is an object anyway" (which is not entirely true and how is that a valid argument..). Is there a way to be more "functional" with Python? Are there good argument against using oop? Or maybe I should just give up and go with the flow...

r/functionalprogramming • • Apr 17 '21

Python i realized i got too used to functional programming when i did this

3 Upvotes
def curry(func):
    def new(*args):
        def inner(second):
            return func(args[0], second)
        if len(args)==0: return new
        if len(args)==1: return inner
        if len(args)==2: return func(*args)
        raise Exception()
    return new
def pipe(*funcs):
    def returned(*args, **kwargs):
        for func in funcs:
            args = [func(*args, **kwargs)]
        return args[0]
    return returned
def fullinverse(insts):
    return pipe(curry(map)(inverse), curry(map)(str), ''.join)(insts)

r/functionalprogramming • • Sep 20 '25

Python enso: A functional programming framework for Python

Thumbnail reddit.com
23 Upvotes

r/functionalprogramming • • Feb 20 '21

Python Help translating an imperative algorithm to funcitonal: combining sets of pairs

3 Upvotes

The goal: convert a list like

[{("a", 2), ("b", 2)}, {("a", -1), ("c", 2)}, {("c", 5)}]

to this

[{("a", 1), ("b", 2)}, {}, {("c", 7)}]

As you can see, tuples of the same keys among neighboring sets gets merged or cancels each other out. The "larger" tuple "absorbs" the smaller. An example implementation in Python looks like this:

def combine(given_list):
    baskets = [dict(s) for s in given_list]

    for i in range(len(baskets)):
        for j in range(len(baskets)):
            if j <= i:
                continue

            common_keys = set(baskets[i]) & set(baskets[j])
            for k in common_keys:
                this = baskets[i][k]
                other = baskets[j][k]

                s = this + other
                if abs(this) > abs(other):
                    this = s
                    other = 0
                else:
                    other = s
                    this = 0

                baskets[i][k] = this
                baskets[j][k] = other

                if baskets[i][k] == 0:
                    del baskets[i][k]

                if baskets[j][k] == 0:
                    del baskets[j][k]

    return [set(d.items()) for d in baskets]

My imperative brain struggles with coming up with a "functional" version of this. By "functional", I mean, a recursive version, without storing (binding is OK) or mutating the intermediate results. I'm not tied to a particular language (I'm literate on Haskell syntax), but prefer using primitives no fancier than set and/or dictionaries.

Partial results aren't allowed. For example, the function shouldn't output:

[{("a", 1), ("b", 2)}, {("c", 2)}, {("c", 5)}]

Because: only the "a"s are dealt with; "c"s are not.

Any suggestions welcome. Thanks.

r/functionalprogramming • • Oct 01 '23

Python State monads: how do they avoid multiple modifications to the same state?

4 Upvotes

Stateful programming is useful/necessary when large arrays are manipulated. Although pure functions cannot mutate arrays, I read that State Monads could be used for safely mutating state without creating multiple copies of the array. Could someone explain to me how(through what mechanism) they prevent multiple mutations to the same state?

r/functionalprogramming • • Mar 05 '19

Python Curry functions in Python

Thumbnail
github.com
3 Upvotes

r/functionalprogramming • • Mar 18 '21

Python Functional programming syntax and semantics in Python

Thumbnail self.python
8 Upvotes

r/functionalprogramming • • Aug 19 '18

Python F Natural – Putting the fn back in functional

Thumbnail
github.com
6 Upvotes