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.fp.option import Just, Maybe, Wrong
from xotl.tools.params import single
from xotl.tools.symbols import boolean
# 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.clipping import small
from xotl.tools.fp.option import Just, Wrong
from xotl.tools.params import single
from xotl.tools.symbols import Invalid, boolean
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