Source code for xotl.tools.fp.prove

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ---------------------------------------------------------------------
# Copyright (c) Merchise Autrement [~º/~] and Contributors
# All rights reserved.
#
# This is free software; you can do what the LICENCE file allows you to.
#

"""Validity proofs for data values.

There are some basic helper functions:

- `predicative`:func: wraps a function in a way that a logical false value is
  returned on failure.  If an exception is raised, it is returned wrapped as
  an special false value.  See `~xotl.tools.fp.option.Maybe`:class: monad for
  more information.

- `vouch`:func: wraps a function in a way that an exception is raised if
  an invalid value (logical false by default) is returned.  This is useful to
  call functions that use "special" false values to signal a failure.

- `enfold`:func: creates a decorator to convert a function to use either the
  `predicative`:func: or the `vouch`:func: protocol.

.. versionadded:: 1.8.0

"""


[docs]def predicative(function, *args, **kwds): """Call a function in a safety wrapper returning a false value if fail. This converts any function into a predicate. A predicate can be thought as an operator or function that returns a value that is either true or false. Predicates are sometimes used to indicate set membership: on certain occasions it is inconvenient or impossible to describe a set by listing all of its elements. Thus, a predicate ``P(x)`` will be true or false, depending on whether x belongs to a set. If the argument `function` validates its arguments, return a valid true value. There are two special conditions: first, a value treated as false for Python conventions (for example, ``0``, or an empty string); and second, when an exception is raised; in both cases the predicate will return an instance of `~xotl.tools.fp.option.Maybe`:class:. """ from xotl.tools.symbols import boolean from xotl.tools.fp.option import Maybe, Just, Wrong from xotl.tools.params import single # I don't understand anymore why a single argument must be a special case, # maybe because the composition problem. is_single = single(*args, **kwds) try: res = function(*args, **kwds) if isinstance(res, (boolean, Maybe)): if isinstance(res, Just) and res.inner: return res.inner elif isinstance(res, boolean) and is_single and args[0]: return args else: return res elif res: return res else: return Just(res) except Exception as error: if isinstance(error, ValueError) and is_single: return Wrong(args[0]) else: return Wrong(error)
[docs]def vouch(function, *args, **kwds): """Call a function in a safety wrapper raising an exception if it fails. When the wrapped function fails, an exception must be raised. A predicate fails when it returns a false value. To avoid treat false values of some types as fails, use `Just`:class: to return that values wrapped. """ from xotl.tools.symbols import boolean, Invalid from xotl.tools.clipping import small from xotl.tools.fp.option import Just, Wrong from xotl.tools.params import single res = function(*args, **kwds) if isinstance(res, boolean): if res: aux = single(*args, **kwds) if aux is not Invalid: res = aux else: msg = "{}() validates as false".format(small(function)) raise TypeError(msg) elif isinstance(res, Wrong): inner = res.inner if isinstance(inner, BaseException): raise inner else: msg = "{}() validates as a wrong value".format(small(function)) if inner is not None or not isinstance(inner, boolean): v, t = small(inner), type(inner).__name__ msg += ' {} of type "{}"'.format(v, t) raise TypeError(msg) elif isinstance(res, Just): res = res.inner return res
[docs]def enfold(checker): """Create a decorator to execute a function inner a safety wrapper. :param checker: Could be any function to enfold, but it's intended mainly for `predicative`:func: or `vouch`:func: functions. In the following example, the semantics of this function can be seen. The definition:: >>> @enfold(predicative) ... def test(x): ... return 1 <= x <= 10 >>> test(5) 5 It is equivalent to:: >>> def test(x): ... return 1 <= x <= 10 >>> predicative(test, 5) 5 In other hand:: >>> @enfold(predicative) ... def test(x): ... return 1 <= x <= 10 >>> test(15) 5 """ def wrapper(func): def inner(*args, **kwds): return checker(func, *args, **kwds) try: inner.__name__ = func.__name__ inner.__doc__ = func.__doc__ except Exception: from xotl.tools.clipping import small inner.__name__ = str(small(func)) return inner return wrapper