I’m a functional programmer at heart, so I’m quite happy thinking in terms of things like functors and monads. I use F# a lot which has computation expressions which is nice syntax for using monads without having to write bind continuations everywhere.

let final = Result {
    let! x = some_function_that_returns_result()
    let y = some_normal_function(x)
    return y

final in the above code will either be Error if some_function_that_returns_result returned an error, or it will be Ok with the value of y.

Today I was pointed at generator.send in Python. Which you can abuse use to get a pretty similar flow.

class Result(object):
    __none = object()

    def __init__(self, ok=__none, error=__none):
        self.ok = ok
        self.error = error

    def monadic(func):
        def wrapper(*args, **kwargs):
            gen = func(*args, **kwargs)
            value = None
            while True:
                    result = gen.send(value)
                    if result.ok is not Result.__none:
                        value = result.ok
                        return result
                except StopIteration as ex:
                    return Result(ok=ex.value)

        return wrapper

    def __str__(self):
        if self.ok is not Result.__none:
            return "Ok({})".format(self.ok)
            return "Error({})".format(self.error)

def do_something(value):
    print("Do something, got value {}".format(value))
    return Result(ok=value+4)

def do_something_else(value):
    print("Do something else, got value {}".format(value))
    return Result(ok=value*2)

def do_something_and_maybe_fail(value):
    print("Do something and maybe fail, got value {}".format(value))
    if value <= 10:
        print("Don't fail")
        return Result(ok=value)
        print("Do fail")
        return Result(error="Some bad error")

def example(value):
    x = yield do_something(value)
    y = yield do_something_and_maybe_fail(x)
    z = yield do_something_else(y)
    return z

if __name__ == "__main__":
    print("Run passing example")
    result = example(1)
    print("Final result = {}".format(result))
    print("Run failing example")
    result = example(10)
    print("Final result = {}".format(result))

If you execute the above python you’ll get the following output:

Run passing example
Do something, got value 1
Do something and maybe fail, got value 5
Don't fail
Do something else, got value 5
Final result = Ok(10)

Run failing example
Do something, got value 10
Do something and maybe fail, got value 14
Do fail
Final result = Error(Some bad error)

Isn’t that fun! It’s monadic bind in python and it doesn’t even look that bad.