Source code for smonad.actions

# -*- coding: utf-8 -*-
# Copyright (c) 2012-2014, Philip Xu <pyx@xrefactor.com>
# License: BSD New, see LICENSE for details.
"""smonad.actions - useful monadic actions."""

from .decorators import function, monadic
from .types import Try, Failure, Success
from .types import Just, Nothing
from .utils import identity
import StringIO
import sys
import traceback


def _get_stacktrace():
    t, _, tb = sys.exc_info()
    f = StringIO.StringIO()
    traceback.print_tb(tb, None, f)
    stacktrace = f.getvalue()
    return stacktrace


[docs]def attempt(callable, exception=None): """Call callable and wrap it with appropriate Failure or Success If calling the callable raises an exception, wrap the exception in a Failure. If the keyword argument ``exception`` is not None, only wrap the specified exception. Raise all other exceptions. The stacktrace related to the exception is added to the wrapped exception as the `stractrace` property If the calling the callable does not raise an exception, return Success wrapping the value """ if exception is None: catch_exception = Exception else: catch_exception = exception try: value = callable() return Success(value) except catch_exception as e: stacktrace = _get_stacktrace() e.stacktrace = stacktrace return Failure(e)
@function
[docs]def ftry(failure_handler, success_handler=identity): """Case analysis for ``Try``. This function is named ftry instead of try to avoid collision with the ``try`` keyword. Returns a function that when called with a value of type ``Try``, applies either ``failure_handler`` or ``success_handler`` to that value depending on the type of it. If an incompatible value is passed, a ``TypeError`` will be raised. >>> def log(v): ... print('Got Failure({})'.format(v)) >>> logger = ftry(failure_handler=log) >>> logger(Failure(1)) Got Failure(1) >>> logger(Success(1)) 1 >>> def inc(v): ... return v + 1 >>> act = ftry(log, inc) >>> [act(v) for v in (Failure(0), Success(1), Failure(2), Success(3))] Got Failure(0) Got Failure(2) [None, 2, None, 4] """ @function def analysis(a_try): """Apply handler functions based on value.""" aug_type = type(a_try) if not issubclass(aug_type, Try): raise TypeError( 'applied either on incompatible type: %s' % aug_type) if issubclass(aug_type, Failure): return failure_handler(a_try.value) assert issubclass(aug_type, Success) return success_handler(a_try.value) return analysis
@function
[docs]def tryout(*functions): """Combine functions into one. Returns a monadic function that when called, will try out functions in ``functions`` one by one in order, testing the result, stop and return with the first value that is true or the last result. >>> zero = lambda n: 'zero' if n == 0 else False >>> odd = lambda n: 'odd' if n % 2 else False >>> even = lambda n: 'even' if n % 2 == 0 else False >>> test = tryout(zero, odd, even) >>> test(0) 'zero' >>> test(1) 'odd' >>> test(2) 'even' """ @monadic def trying(*args, **kwargs): """Monadic function that try out functions in order.""" last = None for func in functions: last = func(*args, **kwargs) if last: break return last return trying
@monadic
[docs]def first(sequence, default=Nothing, predicate=None): """Iterate over a sequence, return the first ``Just``. If ``predicate`` is provided, ``first`` returns the first item that satisfy the ``predicate``, the item will be wrapped in a :class:`Just` if it is not already, so that the return value of this function will be an instance of :class:`Maybe` in all circumstances. Returns ``default`` if no satisfied value in the sequence, ``default`` defaults to :data:`Nothing`. >>> from smonad.types import Just, Nothing >>> first([Nothing, Nothing, Just(42), Nothing]) Just(42) >>> first([Just(42), Just(43)]) Just(42) >>> first([Nothing, Nothing, Nothing]) Nothing >>> first([]) Nothing >>> first([Nothing, Nothing], default=Just(2)) Just(2) >>> first([False, 0, True], predicate=bool) Just(True) >>> first([False, 0, Just(1)], predicate=bool) Just(1) >>> first([False, 0, ''], predicate=bool) Nothing >>> first(range(100), predicate=lambda x: x > 40 and x % 2 == 0) Just(42) >>> first(range(100), predicate=lambda x: x > 100) Nothing This is basically a customized version of ``msum`` for :class:`Maybe`, a separate function like this is needed because there is no way to write a generic ``msum`` in python that cab be evaluated in a non-strict way. The obvious ``reduce(operator.add, sequence)``, albeit beautiful, is strict, unless we build up the sequence with generator expressions in-place. Maybe (pun intended!) implemented as ``MonadOr`` instead of ``MonadPlus`` might be more semantically correct in this case. """ if predicate is None: predicate = lambda m: m and isinstance(m, Just) for item in sequence: if predicate(item): if not isinstance(item, Just): item = Just(item) return item return default
# As decorators function and monadic turn decorated functions into Function # and Monadic instance objects, respectively, doctest will ignore docstring in # them, the following adds those docstring into testsuite back again, # explicitly. __test__ = { 'attempt': ftry.__doc__, 'ftry': ftry.__doc__, 'tryout': tryout.__doc__, 'first': first.__doc__, }