> I think your (sic) arguing against abstractions in general
No.
> We replaced a manual, verbose and error-prone pattern with a standardized abstraction
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.
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.
> since most abstractions we care about obscure control flow - whether functions, loops or monadic bind
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.
> I don't write my own stack push/enter/pop to call functions, and nor should I have to write out all the other boilerplate control flow patterns I use, when it can be simply programmed into a monad, and left to the compiler.
It sounds like an arguments from the exceptions camp โ "everything would be so much better if we didn't have to check error returns from every subroutine!" But like exceptions over error codes, I think introducing monads is inventing a bigger problem to solve a comparatively small problem.
In addition to tmhedberg's good reply, I'd observe that you are either A: excessively stuck on this particular Python implementation when people are trying to argue that monadic patterns are useful in general (and in fact this Python implementation isn't even correct), or B: tautologically arguing from a base of monadic patterns being difficult to conclude that monadic patterns are difficult.
They aren't. Really. They just historically had one of the single most disastrous PR departments in the history of programming concepts. (Which has gotten better, but the old PR is still floating around the nets, screwing up everyone it meets.) It's just an interface slightly more complicated than the standard Iterable interface that happens to have a lot of useful things that can conform to it, and then a lot of useful things that can be built on it (two statements equally true of Iterable itself). It's a great deal simpler to use them when appropriate than the horrifying complexity of trying to manually implement them over and over that you've just grown so used to you can't see any more.
I do my part by trying to ensure I use the term as an adjective instead of a noun, because "monad" is like "iterable" as a term. It is unfortunate that it inherited its status as a noun from the world of math, where that happens to make sense. It does not in programming.
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.
No.
> We replaced a manual, verbose and error-prone pattern with a standardized abstraction
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.
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.
> since most abstractions we care about obscure control flow - whether functions, loops or monadic bind
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.
> I don't write my own stack push/enter/pop to call functions, and nor should I have to write out all the other boilerplate control flow patterns I use, when it can be simply programmed into a monad, and left to the compiler.
It sounds like an arguments from the exceptions camp โ "everything would be so much better if we didn't have to check error returns from every subroutine!" But like exceptions over error codes, I think introducing monads is inventing a bigger problem to solve a comparatively small problem.
Just my 2ยข.