Last active
January 13, 2020 13:41
-
-
Save jhidding/523f917d00d5bb1c3a6bc00cdc34090d to your computer and use it in GitHub Desktop.
A function decorator for failing computations
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from functools import (wraps) | |
from itertools import (chain) | |
class Failure: | |
"""Signifies a failure in a computation that was wrapped by a `@maybe` | |
decorator.""" | |
def __init__(self, func, fails=None, exception=None): | |
self.name = func.__name__ | |
self.fails = fails | |
self.exception = exception | |
self.trace = [] | |
def __bool__(self): | |
"""Failures are falsy.""" | |
return False | |
def add_trace(self, func): | |
self.trace.append(func.__name__) | |
def __str__(self): | |
msg = "Fail: " + " -> ".join(self.trace + [self.name]) | |
if self.exception is not None: | |
msg += "\n* {}: ".format(type(self.exception).__name__) | |
msg += "\n ".join(l for l in str(self.exception).split('\n')) | |
elif self.fails: | |
msg += "\n* failed arguments:\n " | |
msg += "\n ".join( | |
"{} `{}` ".format(func, source) + "\n ".join( | |
l for l in str(fail).split('\n')) | |
for func, source, fail in self.fails) | |
return msg | |
def is_fail(obj): | |
"""Checks if an object is a Failure.""" | |
return isinstance(obj, Failure) | |
def maybe(func): | |
"""Function decorator. Makes a function failt-tolerant by wrapping | |
the function call in a `try/except` block and checking arguments for | |
previous failures.""" | |
@wraps(func) | |
def maybe_wrapped(*args, **kwargs): | |
# filter for all failures | |
fails = [(func.__name__, k, v) for k, v in | |
chain(enumerate(args), kwargs.items()) | |
if is_fail(v)] | |
if fails: | |
return Failure(func, fails=fails) | |
try: | |
result = func(*args, **kwargs) | |
except Exception as exc: | |
return Failure(func, exception=exc) | |
else: | |
if is_fail(result): | |
result.trace.append(func) | |
return result | |
return maybe_wrapped |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from maybe import maybe, is_fail | |
import math | |
@maybe | |
def reciprocal(x): | |
return 1 / x | |
@maybe | |
def square_root(x): | |
return math.sqrt(x) | |
print(square_root(reciprocal(0))) | |
print(square_root(reciprocal(-1))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment