Quiescent Current logo

Quiescent Current

Subscribe
Archives
August 6, 2025

Beyond ?.: Why Python Should Consider Monadic Composition

Diving into monadic composition as a powerful solution to gracefully handle 'None' in Python.

Handling None gracefully shouldn't require mental gymnastics or syntactic clutter.

The Problem: Python and the Pain of None

If you've written Python for any length of time, you've probably danced around None more than you care to admit.

if user and user.profile and user.profile.avatar:
    url = user.profile.avatar.url
else:
    url = None

This pattern is everywhere. It’s verbose, repetitive, and error-prone. Most modern languages have introduced syntactic relief in the form of optional chaining (?.) and null-coalescing (??) operators. Python almost followed suit.

PEP 505: A Practical, Rejected Solution

PEP 505 proposed exactly this: adding ?., ?[], and ?? to let developers write succinct, safe access chains:

url = user?.profile?.avatar?.url ?? default_url

On paper, it solves the problem elegantly. But Python’s core philosophy “there should be one obvious way to do it” clashes with adding more syntax for one-off problems.

PEP 505 ultimately clashes with Python’s preference for clarity and incremental growth over feature creep.

So, the proposal was rejected, yet the pain remained. However, while many (?most?) languages choose special operators to solve this problem and even Python has in limited use cases such as the walrus operator, special operators are not the only way to solve this pain point.

What If We Went Deeper?

PEP 505 tried to address a specific pain point with specific syntax. But in doing so, it ignored a broader, more powerful abstraction that could generalize not only optional values, but a whole class of context-aware computations.

Let’s talk about monadic composition.

Monadic Composition: The Generalized Solution

Monads get a bad rap in Python circles. Monads are often treated as academic, abstract, or “too functional” for imperative audiences, however at their core, monads are a pattern for composing operations, especially for composition with extra context: failure, logging, side effects, or asynchronous control.

Monads are a pattern for composing operations.

Think of it like this:

Instead of:

if x is not None:
    y = f(x)
    if y is not None:
        z = g(y)
# Notice that you'll get an UnboundLocalError if y was None and you reference z later. Hence, you'll probably initialize z to None

You write:

z = Maybe(x).bind(f).bind(g)

The Maybe monad wraps the value and abstracts the null-checking logic. You define the computation once — the monad handles whether it proceeds or short-circuits.

That’s the magic of monadic composition: composability with context-awareness.The context here being nullable/error’d values.

What Does This Look Like in Python?

Here’s a sketch of a Maybe monad:

class Maybe:
    def __init__(self, value):
        self.value = value

    def bind(self, func):
        if self.value is None:
            return Maybe(None)
        return Maybe(func(self.value))

    def get_or(self, default):
        return self.value if self.value is not None else default

Now instead of deeply nested checks, you write:

result = (
    Maybe(user)
    .bind(lambda u: u.profile)
    .bind(lambda p: p.avatar)
    .bind(lambda a: a.url)
    .get_or(default_url) # "unwrap" the monadic context. 
)

This is explicit, readable, and avoids new syntax. Even more importantly it doesn’t just solve null-checking. The same composition style could be used for logging (Writer monad), error handling (Either), async pipelines, and more.

Why Python Hasn’t Gone This Route (Yet)

So why hasn't monadic composition gained traction in Python?

  • No syntax sugar. In Haskell, do-notation turns nested binds into readable, sequential code. Python lacks an equivalent.

  • No standard library support. Python doesn’t ship with Maybe or Result. Instead, devs rely on third-party libraries like toolz, returns, or pymonad.

  • A preference for concrete, incremental features. Python tends to adopt narrow, practical features (like := or match) rather than deep abstractions from category theory.

That said, there’s nothing stopping developers from using monadic patterns today. Community projects like ilyavf/monadic-composition demonstrate how real-world code becomes cleaner and more expressive when composed this way.

Final Thoughts: From PEP 505 to a Better Future

PEP 505 was a pragmatic attempt at solving a real problem. But it was ultimately a band-aid on a broader wound: the need for clean, composable ways to manage context in our programs.

Monadic composition is that solution.

It may not be built into the language. It may never get a PEP. But as Python continues to evolve, borrowing the spirit of monads — even without the syntax — could lead to more powerful, elegant codebases.

Until then, maybe it’s time we wrote our own Maybe.

References

📝 PEPs and Official Python Docs

  • PEP 505 – None-aware operators

📚 GitHub Repositories

  • ilyavf/monadic-composition
    — A talk and code examples on monadic composition in real-world programming.

  • TRCYX/py_monad_do
    — A library implementing Haskell-style do-notation for monads in Python.

  • dry-python/returns
    — A popular library for typed functional programming in Python, including monadic types.

📦 Python Libraries

  • pymonad on PyPI
    — Monadic constructs for Python, including Maybe, Either, and State.

  • monad-do on PyPI
    — A library to use generator-based do-notation in Python.

📖 Articles and Learning Resources

  • Monadic Composition and Kleisli Arrows – FreeCodeCamp
    — A deeper look at monadic composition using function chaining.

  • Monads in Python – Software Patterns Lexicon
    — Patterns and idioms for implementing and understanding monads in Python.

  • Monads in Python with PyMonad – Miguel Farrajota
    — A tutorial on using the PyMonad library.

Don't miss what's next. Subscribe to Quiescent Current:
GitHub Website LinkedIn
Powered by Buttondown, the easiest way to start and grow your newsletter.