I had the exact same experience! A light went off when I figured out how to elegantly sequence asynchronous calls in Scala, and it was then that I realized I was using Monads to do so.
However, I am curious if python's solution looks ugly (since they don't seem to have a 'do' or 'for' expression that Haskell and Scala have respectively)?
I did the same thing in Ruby [1], but it does indeed look uglier than the equivalent Haskell/Scala because of the lack of language support for monadic syntax.
The benefit of realising the monadic behaviour of asynchronous sequencing (which I call "Deferrable#bind!") was that if you had a bunch of nested callbacks, the monad associativity law [2] meant you could replace them with a simple linear sequence of chained bind! calls:
fetch().bind! do |a|
process(a)
end.bind! do |b|
process(b)
end.bind! do |c|
# ...
end
instead of
fetch().callback do |a|
process(a).callback do |b|
process(b).callback do |c|
# ...
end
end
end
I find the former a lot more readable, partly because the "end" keywords don't all pile up at the end, and especially if you try and do error handling (in the nested case the error handling ends up in reverse order!).
I'm guessing you can't really do a 1:1, although maybe the OP can clarify.
They do stuff like this in jQuery, so I can't imagine it's all that difficult in Python.
My initial thought would be to write something analogous to Maybe. You can't pattern match in Python, which is a shame, but you could make it so that foo.bar().baz().quux() will return MaybeResult, which everything involved in the transaction inherits from. MaybeResult might have a "success" variable on it indicating whether it ought to be treated as Nothing. Otherwise, check "result."
This is naive but it gets you a little bit closer to fault-tolerant chaining of interdependent methods. Not as nice as Haskell, obvs. :)
However, I am curious if python's solution looks ugly (since they don't seem to have a 'do' or 'for' expression that Haskell and Scala have respectively)?