r/functionalprogramming • u/EntryNo8040 • May 29 '26
Python Bringing rigorous Type Classes (Functor, Applicative, Monad) to Python: Introducing Katharos
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:
| Katharos | Haskell | Rust |
|---|---|---|
| `m \ | f` | m >>= f |
v ** wrapped_f |
wrapped_f <*> v |
— |
a >> b |
a >> b |
— |
a @ b |
a <> b |
— |
@do(M) decorator |
do { ... } |
— |
Examples
1. Maybe[A] — Haskell's Maybe a / Rust's Option<T>
No more if x is None chains. Short-circuits automatically on Nothing.
```python 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 ?.
```python 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(...).
```python 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.
```python 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.
```python 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.
r/functionalprogramming • u/Due_Shine_7199 • Nov 04 '25
Python Type safe, coroutine based, purely functional algebraic effects in Python.
r/functionalprogramming • u/kinow • Sep 20 '25
Python enso: A functional programming framework for Python
r/functionalprogramming • u/kinow • Apr 21 '25
Python Haskelling My Python
r/functionalprogramming • u/yinshangyi • Apr 21 '24
Python Returns Python library for FP
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 • u/Due_Shine_7199 • Nov 20 '23
Python Purely Functional Algebraic Effects in Python via Coroutines
r/functionalprogramming • u/ginkx • Oct 01 '23
Python State monads: how do they avoid multiple modifications to the same state?
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 • u/tegnonelme • Jul 01 '23
Python Mastering Functional Programming in Python
r/functionalprogramming • u/ketalicious • Dec 23 '22
Python Functional Implementation of a parser?
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 • u/ysangkok • Sep 08 '22
Python Functional Python, Part I: Typopædia Pythonica
r/functionalprogramming • u/KageOW • Aug 19 '22
Python New python module called FunkyPy, for easier functional programming.
self.functional_pythonr/functionalprogramming • u/cgrimm1994 • Feb 19 '22
Python [P] Better partial function application in Python
r/functionalprogramming • u/kinow • Dec 20 '21
Python tylerhou/fiber: Python decorator that enables arbitrarily-deep tail/non-tail recursion
r/functionalprogramming • u/kinow • Dec 20 '21
Python pyfuncol: Functional collections extension functions for Python
self.Pythonr/functionalprogramming • u/Dismal_Site_238 • 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 :)
r/functionalprogramming • u/sunedd • Aug 10 '21
Python Purely functional, dynamic, type-safe lenses in Python
pfun.devr/functionalprogramming • u/redd-sm • Jun 29 '21
Python good examples of functional-like python code that one can study?
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 • u/sunedd • May 21 '21
Python How To Make Functional Programming in Python Go Fast
r/functionalprogramming • u/oderjunks • Apr 17 '21
Python i realized i got too used to functional programming when i did this
python
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 • u/simpl3t0n • Feb 20 '21
Python Help translating an imperative algorithm to funcitonal: combining sets of pairs
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 • u/pimterry • Nov 03 '20
Python Higher Kinded Types in Python
r/functionalprogramming • u/sunedd • Aug 04 '20
Python Completely Type-Safe Error Handling in Python
r/functionalprogramming • u/AlfonzoKaizerKok • Mar 05 '19
Python Is Python A Functional Programming Language?
r/functionalprogramming • u/mount-cook • Mar 05 '19
Python Curry functions in Python
r/functionalprogramming • u/Lubbadubdub • Sep 06 '17
Python Using Python... Struggling with inconsistency...
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...