Welcome to Monad’s documentation!¶
Contents:
monad - a functional python package¶
Note
This project is superseded by Hymn(https://github.com/pyx/hymn).
Limited by Python’s syntax, there is no way to have a clean implementation
of do notation, the closest thing is a do
decorator on generator
functions using yield
as <-
, which feels like black magic.
That’s why I stopped shoehorning this into Python, and did a complete rewrite in Hy (https://github.com/hylang/hy) a few years ago.
Being a lisp, or as they say, Homoiconic Python, Hy has the most flexible
syntax (or lack thereof :smile:), with it, I finally can write do notations,
check this out (for added fun, a Lazy
monad is being demonstrated here,
we can never have such clean way to write thunk in pure python):
=> (import [hymn.types.lazy [force]])
=> (require [hymn.types.lazy [lazy]])
=> ;; lazy computation implemented as monad
=> ;; macro lazy creates deferred computation
=> (setv a (lazy (print "evaluate a") 42))
=> ;; the computation is deferred, notice the value is shown as '_'
=> a
Lazy(_)
=> ;; evaluate it
=> (.evaluate a)
evaluate a
42
=> ;; now the value is cached
=> a
Lazy(42)
=> ;; calling evaluate again will not trigger the computation
=> (.evaluate a)
42
=> (setv b (lazy (print "evaluate b") 21))
=> b
Lazy(_)
=> ;; force evaluate the computation, same as calling .evaluate on the monad
=> (force b)
evaluate b
21
=> ;; force on values other than lazy return the value unchanged
=> (force 42)
42
=> (require [hymn.macros [do-monad]])
=> ;; do notation with lazy monad
=> (setv c (do-monad [x (lazy (print "get x") 1) y (lazy (print "get y") 2)] (+ x y)))
=> ;; the computation is deferred
=> c
Lazy(_)
=> ;; do it!
=> (force c)
get x
get y
3
=> ;; again
=> (force c)
3
So, if you are interested in this package, please try Hymn(https://github.com/pyx/hymn) instead.
Introduction¶
What?¶
Monads in python, with some helpful functions.
How?¶
>>> from monad.decorators import maybe
>>> parse_int = maybe(int)
>>> parse_int(42)
Just(42)
>>> parse_int('42')
Just(42)
>>> parse_int('42.2')
Nothing
>>> parse_float = maybe(float)
>>> parse_float('42.2')
Just(42.2)
>>> from monad.actions import tryout
>>> parse_number = tryout(parse_int, parse_float)
>>> tokens = [2, '0', '4', 'eight', '10.0']
>>> [parse_number(token) for token in tokens]
[Just(2), Just(0), Just(4), Nothing, Just(10.0)]
>>> @maybe
... def reciprocal(n):
... return 1. / n
>>> reciprocal(2)
Just(0.5)
>>> reciprocal(0)
Nothing
>>> process = parse_number >> reciprocal
>>> process('4')
Just(0.25)
>>> process('0')
Nothing
>>> [process(token) for token in tokens]
[Just(0.5), Nothing, Just(0.25), Nothing, Just(0.1)]
>>> [parse_number(token) >> reciprocal for token in tokens]
[Just(0.5), Nothing, Just(0.25), Nothing, Just(0.1)]
>>> [parse_number(token) >> reciprocal >> reciprocal for token in tokens]
[Just(2.0), Nothing, Just(4.0), Nothing, Just(10.0)]
Why?¶
Why not.
Requirements¶
- CPython >= 2.7
Installation¶
Install from PyPI:
pip install monad
Install from source, download source package, decompress, then cd
into source directory, run:
make install
License¶
BSD New, see LICENSE for details.
Links¶
- Documentation:
- http://monad.readthedocs.org/
- Issue Tracker:
- https://bitbucket.org/pyx/monad/issues/
- Source Package @ PyPI:
- https://pypi.python.org/pypi/monad/
- Mercurial Repository @ bitbucket:
- https://bitbucket.org/pyx/monad/
- Git Repository @ Github:
- https://github.com/pyx/monad/
API Reference¶
monad - a functional library
Actions¶
monad.actions - useful monadic actions.
-
monad.actions.
either
(left_handler, right_handler=identity)[source]¶ Case analysis for
Either
.Returns a function that when called with a value of type
Either
, applies eitherleft_handler
orright_handler
to that value depending on the type of it. If an incompatible value is passed, aTypeError
will be raised.>>> def log(v): ... print('Got Left({})'.format(v)) >>> logger = either(left_handler=log) >>> logger(Left(1)) Got Left(1) >>> logger(Right(1)) 1 >>> def inc(v): ... return v + 1 >>> act = either(log, inc) >>> [act(v) for v in (Left(0), Right(1), Left(2), Right(3))] Got Left(0) Got Left(2) [None, 2, None, 4]
-
monad.actions.
first
(sequence, default=Nothing, predicate=None)[source]¶ Iterate over a sequence, return the first
Just
.If
predicate
is provided,first
returns the first item that satisfy thepredicate
, the item will be wrapped in aJust
if it is not already, so that the return value of this function will be an instance ofMaybe
in all circumstances. Returnsdefault
if no satisfied value in the sequence,default
defaults toNothing
.>>> from monad.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
forMaybe
, a separate function like this is needed because there is no way to write a genericmsum
in python that cab be evaluated in a non-strict way. The obviousreduce(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 ofMonadPlus
might be more semantically correct in this case.
-
monad.actions.
tryout
(*functions)[source]¶ 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'
Decorators¶
monad.decorators - helpful decorators.
-
monad.decorators.
failsafe
(callable_object=None, predicate=None, left_on_value=Null, left_on_exception=<type 'exceptions.Exception'>)[source]¶ Transform a callable into a function returns an
Either
.>>> parse_int = failsafe(int) >>> parse_int(42) Right(42) >>> parse_int(42.0) Right(42) >>> parse_int('42') Right(42) >>> parse_int('invalid') Left(ValueError(...))
>>> parse_pos = failsafe(int, predicate=lambda i: i > 0) >>> parse_pos('42') Right(42) >>> parse_pos('-42') Left(-42)
>>> parse_nonzero = failsafe(int, left_on_value=0) >>> parse_nonzero('42') Right(42) >>> parse_nonzero('0') Left(0)
>>> @failsafe(left_on_exception=ZeroDivisionError) ... def safe_div(a, b): ... return a / b >>> safe_div(42.0, 2) Right(21.0) >>> safe_div(42, 0) Left(ZeroDivisionError(...))
When invoked, this new function returns the return value of decorated function, wrapped in an
Either
monad.predicate
should be a false value, or be set to a callable. The default isNone
.left_on_value
can be set to any object supporting comparison against return value of the original function. The default isNull
, which means no checking on the return value.left_on_exception
should be a false value, or a type of exception, or a tuple of exceptions. The default isException
, which will suppress most exceptions and returnLeft(exception)
instead.The returned monad will be
Left
ifpredicate
is set, andpredicate(result_from_decorated_function)
returns true value (not necessarily equal toTrue
)left_on_value
is set and the result from decorated function matches it, testing with==
left_on_exception
is set and a compatible exception has been caught, the exception will be suppressed in this case, and the value of exception will be wrapped in aLeft
- exception
ExtractError
has been caught, this could be the case, for example, trying to extract value fromNothing
- any combination of the above
Otherwise, the result will be wrapped in a
Right
.
-
monad.decorators.
function
(callable_object)[source]¶ Decorator that wraps a callabe into
Function
.>>> to_int = function(int) >>> to_int('42') 42 >>> @function ... def puts(msg, times=1): ... while times > 0: ... print(msg) ... times -= 1 >>> puts('Hello, world', 2) Hello, world Hello, world
-
monad.decorators.
maybe
(callable_object=None, predicate=None, nothing_on_value=Null, nothing_on_exception=<type 'exceptions.Exception'>)[source]¶ Transform a callable into a function returns a
Maybe
.>>> parse_int = maybe(int) >>> parse_int(42) Just(42) >>> parse_int(42.0) Just(42) >>> parse_int('42') Just(42) >>> parse_int('invalid') Nothing
>>> parse_pos = maybe(int, predicate=lambda i: i > 0) >>> parse_pos('42') Just(42) >>> parse_pos('-42') Nothing
>>> parse_nonzero = maybe(int, nothing_on_value=0) >>> parse_nonzero('42') Just(42) >>> parse_nonzero('0') Nothing
>>> @maybe(nothing_on_exception=ZeroDivisionError) ... def safe_div(a, b): ... return a / b >>> safe_div(42.0, 2) Just(21.0) >>> safe_div(42, 0) Nothing
When invoked, this new function returns the return value of decorated function, wrapped in a
Maybe
monad.predicate
should be a false value, or be set to a callable. The default isNone
.nothing_on_value
can be set to any object supporting comparison against return value of the original function. The default isNull
, which means no checking on the return value.nothing_on_exception
can be a false value, a type of exception, or a tuple of exceptions. The default isException
, which will suppress most exceptions and returnNothing
instead.The returned monad will be
Nothing
ifpredicate
is set, andpredicate(result_from_decorated_function)
returns true value (not necessarily equal toTrue
)nothing_on_value
is set and the result from decorated function matches it, testing with==
nothing_on_exception
is set and a compatible exception has been caught, the exception will be suppressed in this case- exception
ExtractError
has been caught, when trying to extract value fromNothing
- any combination of the above
Otherwise, the result will be wrapped in a
Just
.
-
monad.decorators.
producer
(function_or_generator=None, empty_on_exception=None)[source]¶ Transform a callable into a producer that when called, returns
List
.>>> @producer ... def double(a): ... yield a ... yield a >>> List(42) >> double List(42, 42)
>>> @producer ... def times(a): ... for b in List(1, 2, 3): ... yield '{}x{}={}'.format(a, b, a * b) >>> List(1, 2) >> times List('1x1=1', '1x2=2', '1x3=3', '2x1=2', '2x2=4', '2x3=6')
function_or_generator
can be a function that returns an iterable, or a generator.empty_on_exception
can be a false value, a type of exception, or a tuple of exceptions. The default isNone
, which will not suppress all exceptions exceptExtractError
, in which case, an emptyList
will be returned.
Exceptions¶
monad.exceptions - custom exceptions.
Common Mixin Classes¶
monad.mixins - implements common mixin classes.
Types¶
The Null Object¶
monad.types.null - The Null type.
Lazy Sequence¶
monad.types.lazysequence - a sequence type with lazy evaluation.
-
class
monad.types.lazysequence.
LazySequence
(iterable)[source]¶ Bases:
_abcoll.Sequence
Sequence with lazy evaluation.
>>> from itertools import count >>> seq = LazySequence(count()) >>> seq[1] 1 >>> list(seq[3:5]) [3, 4] >>> list(seq[:20:2]) [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
-
strict
¶ Proxy to self that forces evaluation when accessed.
-
Functor¶
monad.types.functor - The Functor Class.
Applicative Functor¶
monad.types.applicative - The Applicative Functor Class.
-
class
monad.types.applicative.
Applicative
(value)[source]¶ Bases:
monad.types.functor.Functor
The Applicative Functor Class.
Defines the following functions:
unit
which act as constructor, it’s calledpure
in some context.
-
unit
= NotImplemented¶ The unit.
Maps a value to a value in this type. Also called
pure
orreturn
depends on context.
Function¶
monad.types.function - The Function Wrapper.
-
class
monad.types.function.
Function
(callable_object)[source]¶ Bases:
object
The Function Wrapper.
Support function composition via
*
operator.>>> add_1 = Function(lambda n: n + 1) >>> inc = add_1 * int >>> inc('42') 43
Support function piping via
|
operator.>>> inc2 = int | add_1 | add_1 | str >>> inc2('42') '44'
Monadic Function¶
monad.types.monadic - The Monadic Fuction Wrapper.
-
class
monad.types.monadic.
Monadic
(callable_object)[source]¶ Bases:
monad.types.function.Function
The Monadic Function Wrapper.
Implements Kleisli composition operators
>>
and<<
. It is equivalent to(>=>)
and(<=<)
in haskell.
Monad¶
monad.types.monad - The Monad Class.
-
class
monad.types.monad.
Monad
(value)[source]¶ Bases:
monad.types.applicative.Applicative
The Monad Class.
Implements bind operator
>>
and inverted bind operator<<
as syntactic sugar. It is equivalent to(>>=)
and(=<<)
in haskell, not to be confused with(>>)
and(<<)
in haskell.As python treats assignments as statements, there is no way we can overload
>>=
as a chainable bind, be it directly overloaded through__irshift__
, or derived by python itself through__rshift__
.The default implementations of
bind
,fmap
andjoin
are mutual recursive, subclasses should at least either overloadbind
, orfmap
andjoin
, or all of them for better performance.-
bind
(function)[source]¶ The bind operation.
function
is a function that maps from the underlying value to a monadic type, something like signaturef :: a -> M a
in haskell’s term.The default implementation defines
bind
in terms offmap
andjoin
.
-
fmap
(function)[source]¶ The fmap operation.
The default implementation defines
fmap
in terms ofbind
andunit
.
-
Monad Plus¶
monad.types.monadplus - The MonadPlus Class.
-
class
monad.types.monadplus.
MonadPlus
(value)[source]¶ Bases:
monad.types.monad.Monad
The MonadPlus Class.
Monads that also support choice and failure.
-
zero
= NotImplemented¶ The identity of
plus
.This property should be a singleton, the following must be
True
:MP.zero is MP.zero
It should satisfy the following law, left zero (notice the bind operator is haskell’s
>>=
here):zero >>= f = zero
-
The Identity Monad¶
monad.types.identity - The Identity Monad.
-
class
monad.types.identity.
Identity
(value)[source]¶ Bases:
monad.types.monad.Monad
,monad.mixins.ContextManager
,monad.mixins.Ord
The Identity Monad.
>>> Identity(42) Identity(42) >>> Identity([1, 2, 3]) Identity([1, 2, 3])
Comparison with
==
, as long as what’s wrapped inside are comparable.>>> Identity(42) == Identity(42) True >>> Identity(42) == Identity(24) False
The Maybe Monad¶
monad.types.maybe - The Maybe Monad.
-
monad.types.maybe.
Just
¶ alias of
monad.types.maybe.Maybe
-
class
monad.types.maybe.
Maybe
(value)[source]¶ Bases:
monad.types.monadplus.MonadPlus
,monad.mixins.ContextManager
,monad.mixins.Ord
The Maybe Monad.
Representing values/computations that may fail.
>>> Just(42) Just(42) >>> Just([1, 2, 3]) Just([1, 2, 3]) >>> Just(Nothing) Just(Nothing) >>> Just(Just(2)) Just(Just(2)) >>> isinstance(Just(1), Maybe) True >>> isinstance(Nothing, Maybe) True >>> saving = 100 >>> spend = lambda cost: Nothing if cost > saving else Just(saving - cost) >>> spend(90) Just(10) >>> spend(120) Nothing >>> safe_div = lambda a, b: Nothing if b == 0 else Just(a / b) >>> safe_div(12.0, 6) Just(2.0) >>> safe_div(12.0, 0) Nothing
Bind operation with
>>
>>> inc = lambda n: Just(n + 1) if isinstance(n, int) else Nothing >>> Just(0) Just(0) >>> Just(0) >> inc Just(1) >>> Just(0) >> inc >> inc Just(2) >>> Just('zero') >> inc Nothing
Comparison with
==
, as long as what’s wrapped inside are comparable.>>> Just(42) == Just(42) True >>> Just(42) == Nothing False >>> Nothing == Nothing True
-
bind
(function)[source]¶ The bind operation of
Maybe
.Applies function to the value if and only if this is a
Just
.
-
The Either Monad¶
monad.types.either - The Either Monad.
-
class
monad.types.either.
Either
(value)[source]¶ Bases:
monad.types.monad.Monad
,monad.mixins.ContextManager
,monad.mixins.Ord
The Either Monad.
Represents values/computations with two possibilities.
>>> Right(42) Right(42) >>> Right([1, 2, 3]) Right([1, 2, 3]) >>> Left('Error') Left('Error') >>> Right(Left('Error')) Right(Left('Error')) >>> isinstance(Right(1), Either) True >>> isinstance(Left(None), Either) True >>> saving = 100 >>> broke = Left('I am broke') >>> spend = lambda cost: broke if cost > saving else Right(saving - cost) >>> spend(90) Right(10) >>> spend(120) Left('I am broke') >>> safe_div = lambda a, b: Left(str(a) + '/0') if b == 0 else Right(a / b) >>> safe_div(12.0, 6) Right(2.0) >>> safe_div(12.0, 0) Left('12.0/0')
Bind operation with
>>
>>> inc = lambda n: Right(n + 1) if type(n) is int else Left('Type error') >>> Right(0) Right(0) >>> Right(0) >> inc Right(1) >>> Right(0) >> inc >> inc Right(2) >>> Right('zero') >> inc Left('Type error')
Comparison with
==
, as long as they are the same type and what’s wrapped inside are comparable.>>> Left(42) == Left(42) True >>> Right(42) == Right(42) True >>> Left(42) == Right(42) False
A
Left
is less than aRight
, or compare the two by the values inside if thay are of the same type.>>> Left(42) < Right(42) True >>> Right(0) > Left(100) True >>> Left('Error message') > Right(42) False >>> Left(100) > Left(42) True >>> Right(-2) < Right(-1) True
-
bind
(function)[source]¶ The bind operation of
Either
.Applies function to the value if and only if this is a
Right
.
-
unit
= <monad.types.monadic.Monadic object>¶
-
-
class
monad.types.either.
Left
(value)[source]¶ Bases:
monad.types.either.Either
Left of
Either
.
-
class
monad.types.either.
Right
(value)[source]¶ Bases:
monad.types.either.Either
Right of
Either
.
The List Monad¶
monad.types.list - The List Monad.
-
class
monad.types.list.
List
(*items)[source]¶ Bases:
monad.types.monadplus.MonadPlus
,monad.mixins.Ord
,_abcoll.Sequence
The List Monad.
Representing nondeterministic computation.
>>> List(42) List(42) >>> List(1, 2, 3) List(1, 2, 3) >>> List([]) List([]) >>> List.from_iterable(range(3)) List(0, 1, 2) >>> List.from_iterable(n for n in (1, 2, 3) if n % 2 == 0) List(2) >>> List(List(2)) List(List(2))
Lists are lazy
>>> from itertools import count >>> m = List.from_iterable(count()) >>> m[:5] List(0, 1, 2, 3, 4) >>> m[520:524] List(520, 521, 522, 523) >>> list(m[1000:1002]) [1000, 1001]
Bind operation with
>>
>>> spawn = lambda cell: List(cell, cell) >>> spawn('c') List('c', 'c') >>> spawn('c') >> spawn List('c', 'c', 'c', 'c') >>> grow = lambda cell: List(cell + '~') >>> grow('o') List('o~') >>> grow('o') >> grow >> grow >> grow List('o~~~~') >>> generation = lambda cell: grow(cell) + spawn(cell) >>> first = List('o') >>> first List('o') >>> first >> generation List('o~', 'o', 'o') >>> first >> generation >> generation List('o~~', 'o~', 'o~', 'o~', 'o', 'o', 'o~', 'o', 'o')
Utility Functions¶
monad.utils - utility functions and values.
-
class
monad.utils.
SuppressContextManager
(*exceptions)[source]¶ Bases:
object
Context manager class that suppress specified exceptions.
Changelog¶
0.1
First public release.