Alternatively, we replaced some easy to follow procedural code with a manual, verbose, and error-prone abstraction. I have no idea what is going on with the author's decorated classes, generators, etc.

It's complicated and fairly ugly to do this in any language which has no first-class support for the monad abstraction. I wouldn't use this code in a "real world" setting, and I doubt even the author would argue that you should. Many of Haskell's unique language features make monads "go down smoothly" in a way that most other languages will only be able to roughly imitate without extensions like user-definable operators, type classes, `do` notation, return type and higher-ranked polymorphism, etc.

How do you create a new monad pattern and debug it? All code is imperfect; it feels like we're just shifting some of the logic around to a different place.

Nearly all forms of abstraction in any programming language can be characterized as "shifting some of the logic around to a different place". The entire point of abstraction is to hide away complexity behind abstract interfaces so that we can build more and more complex systems without having to mentally wrangle with the entire stack of complexity all at once. For instance, most people writing code in a high-level language don't spend much time mentally compiling the code they're writing down to machine instructions; we trust that the language interpreter or compiler will take care of that complexity well enough for us so that we can focus on the bigger picture. Monads just ratchet up this level of abstraction one notch higher than most languages take it.

Monads are the very same sort of thing that lets us jump to other locations via function calls, or encapsulate shared mutable state in a class (in OOP languages). The only thing that makes them different is that the "call stack" and "class" abstractions are given first-class support in the majority of popular languages, while monads are not.

Furthermore, in a strongly typed language with support for monads, the implicit flow control in a series of monadic statements is made obvious by its type. When encountering a `do` block of type `Maybe x`, there is no ambiguity surrounding the fact that each statement in the block may fail, and if it does, will short-circuit the remainder of the computation. This is not so much "hidden" control flow as it is "syntactically unencumbered". It would take a significant lapse of attention to forget about the monadic context of the computation and its implications (and if you did forget, the compiler would tell you about it). No one complains that when you encounter a statement like `foo.bar()` in an OO language, you can't know exactly which definition of `bar` is being called without walking up the inheritance chain to find it. There is implicit "magic" taking place here as well, in the form of dynamic method dispatch, but because most programmers encounter this particular sort of implicit behavior every day, it doesn't seem so strange or dangerous.

This is apples and oranges. Functions and loops are straight-forward, monadic bind (in this framework) involves indirecting through a bunch of complicated Python magic.

Functions and loops seem more straightforward to you because you are used to using them in the languages you use regularly and are most comfortable with. To a reasonably experienced Haskell user, there is nothing about monads that is not equally straightforward or obvious. More than anything else, it's a question of familiarity.

I won't argue that the Python implementation isn't complicated or somewhat "magical". I just don't think that's a result of monads' inherent complexity; rather, it is the result of the fact that Python isn't designed for working with this particular kind of abstraction. Any implementation of it in a language like Python is bound to be a bit kludgey and difficult to follow.