New in version 0.1.
the fp.monads module provides a collection of monads to use with your Python code.
Maybe
fp.monads provides an implementation of the Maybe monad.
A Maybe monad.
The Maybe monad is helpful when dealing with sequences of functions that could return None.
For instance, when fetching a key from a dictionary that may not be there:
>>> from fp.monads.maybe import Maybe, Just, Nothing
>>> data = {"foo": {"bar": {"baz": "bing"}}}
>>> data['foo']['bong']['baz']
Traceback (most recent call last):
...
KeyError: 'bong'
This is a very common problem when processing JSON. Often, with JSON, a missing key is the same as null but in Python a missing key raises an error.
dict.get is often the solution for missing keys but is still not enough:
>>> data.get("foo").get("bong").get("baz")
Traceback (most recent call last):
...
AttributeError: 'NoneType' object has no attribute 'get'
To final solution to this ends up being ugly:
>>> data.get("foo", {}).get("bong", {}).get("baz") is None
True
It is even more complex when dealing with mixed types; for instance if “bong” ends up being a list instead of a dict.
The Maybe monad lets us express errors such as these as either something or nothing. This is much like dict.get() returning None, but we can chain Maybe actions so that they fail with Nothing, if any of the functions in the chain returns Nothing.
Normally getitem will raise an error if the key or index is invalid, Maybe.catch() causes Nothing to be returned if any exception occurs.
>>> from operator import getitem
>>> from fp import p
>>> lookup = p(Maybe.catch, getitem)
>>> lookup({"foo": "bar"}, "foo")
Just('bar')
>>> lookup({"foo": "bar"}, "bong")
Nothing
Now lookup returns Just(x) if the key is found or Nothing if the key is not found.
To extract the value out of the Just() class, we can call Maybe.default():
>>> lookup({'foo': 'bar'}, 'foo').default('')
'bar'
>>> lookup({'foo': 'bar'}, 'bong').default('')
''
To chain the lookups, we simply need to partially apply the lookup function:
>>> from fp import pp
>>> lookup(data, "foo").bind(pp(lookup, "bar")).bind(pp(lookup, "baz"))
Just('bing')
>>> lookup(data, "foo").bind(pp(lookup, "bong")).bind(pp(lookup, "baz"))
Nothing
This is still not as pretty as it could be so we provide a get_nested function in the fp.collections module:
>>> from fp.collections import get_nested
>>> get_nested(Maybe, data, "foo", "bar", "baz")
Just('bing')
>>> get_nested(Maybe, data, "foo", "bong", "baz")
Nothing
In addition, functions that return None become Nothing automatically:
>>> Maybe.ret(None)
Nothing
>>> Maybe.ret({}.get('foo'))
Nothing
This feature allows you to easily integrate the Maybe monad with existing functions that return None.
The Maybe’s bind function. f is called only if self is a Just.
>>> Just(1).bind(lambda x: Maybe(x))
Just(1)
>>> def crashy(x):
... assert False, "bind will not call me"
>>> Nothing.bind(crashy)
Nothing
Returns an iterator of the Just values
>>> list(Maybe.cat_maybes([
... Just(1), Nothing, Just(2)
... ]))
[1, 2]
Returns the just value or the default value if Nothing
>>> Just(1).default(-1)
1
>>> Nothing.default(-1)
-1
>>> Maybe.fail("some error")
Nothing
Extracts the just value from the Maybe; raises a ValueError if the value is None
>>> Just(1).from_just
1
>>> Nothing.from_just
Traceback (most recent call last):
...
ValueError: Maybe.from_just called on Nothing
Tests if the Maybe is just a value
>>> Just(1).is_just
True
>>> Nothing.is_just
False
Tests if the Maybe is Nothing
>>> Nothing.is_nothing
True
>>> Just(1).is_nothing
False
maps a list over a Maybe arrow
>>> def maybe_even(x):
... if x % 2 == 0:
... return Just(x)
... else:
... return Nothing
>>> list(Maybe.map_maybes(maybe_even, [1,2,3,4]))
[2, 4]
An associative operation.
>>> Just(1).mplus(Maybe.mzero)
Just(1)
>>> Nothing.mplus(Maybe(2))
Just(2)
>>> Just(1).mplus(Just(2))
Just(1)
Either
fp.monads provides an implementation of the Either monad.
The Either monad is helpful when dealing with sequences of functions that can fail. These functions return Right(value) on success and Left(error) on failure.
When chained together, these functions will short circuit the chain when a failure occurs.
For instance an example of lookup(dict, key) could be:
>>> def lookup(d, k):
... try:
... return Right(d[k])
... except Exception as err:
... return Left(err)
>>> lookup({'foo': 'bar'}, 'foo')
Right('bar')
>>> lookup({}, 'foo')
Left(KeyError('foo',))
The benefit of Either’s over Exceptions is that you’re forced to handle the error eventually. You can’t let the Exception bubble to the surface and crash your program. Nothing escapes the monad!
The Either method lets you handle the error in your own way.
>>> lookup({}, 'foo').either(
... lambda err: err,
... lambda val: val
... )
KeyError('foo',)
>>> lookup({'foo': 'bar'}, 'foo').either(
... lambda err: err,
... lambda val: val
... )
'bar'
The default method lets you completely ignore the error:
>>> lookup({'foo': 'bar'}, 'foo').default('bing')
'bar'
>>> lookup({}, 'foo').default('bing')
'bing'
Just like any other monad, functions can be chained together using bind(f):
>>> lookup({'foo': {'bar': 'baz'}}, 'foo').bind(
... lambda foo: lookup(foo, 'bar'))
Right('baz')
>>> lookup({'foo': {}}, 'foo').bind(
... lambda foo: lookup(foo, 'bar'))
Left(KeyError('bar',))
>>> lookup({}, 'foo').bind(
... lambda foo: lookup(foo, 'bar'))
Left(KeyError('foo',))
Returns the value if right, default value if left.
Execute the left_fun(err) on Left, right_fun(value) on Right
True if the Either is left
True if the Either is right
Extract the lefts from the list
>>> list(Either.lefts([Left(1), Right(2), Left(3)]))
[Left(1), Left(3)]
Extract the rights from the list
>>> list(Either.rights([Left(1), Right(2), Left(3)]))
[Right(2)]
IO
fp.monads provides an implementation of the IO monad.
This is the IO monad. Useful in composing IO code
This module implements an IO monad. Its usefulness in Python can be argued against (and won easily) but it is implement for completeness.
>>> @io
... def printLn(x):
... print(x)
The IO action is returned when calling printLn but nothing is printed
>>> action = printLn("hi")
>>> isinstance(action, IO)
True
>>> action.run()
hi
Using bind:
>>> printLn("Hello").bind_(
... lambda: printLn("World")
... ).run()
Hello
World
The failure of an IO monad is an exception
>>> IO.fail(KeyError("key"))
Traceback (most recent call last):
...
KeyError: 'key'
Returns a value in the IO monad:
>>> action = IO.ret("Hello")
You can then use bind that to an arrow:
>>> action.bind(printLn).run()
Hello
Base Monad classes
Use the following classes for defining your own monads.
Monad.ap(f, *args, **kwargs) allows you to call a pure function within a Monad.
A normal function like f(x): x + 1 can be made to take a Maybe and return a Maybe without any boilerplating:
>>> from fp.monads.maybe import Maybe, Just, Nothing
>>> Maybe.ap(lambda x: x+1, Just(1))
Just(2)
>>> Maybe.ap(lambda x: x+1, Nothing)
Nothing
Here’s an example with an add function:
>>> import operator
>>> Maybe.ap(operator.add, Just(1), Just(2))
Just(3)
>>> Maybe.ap(operator.add, Nothing, Just(2))
Nothing
>>> Maybe.ap(operator.add, Just(1), Nothing)
Nothing
It even works with kwargs:
>>> from datetime import timedelta
>>> Maybe.ap(timedelta, days=Just(1), seconds=Just(60))
Just(datetime.timedelta(1, 60))
Left to Right arrow composition
This expressions means: maybe cast the string as an int, then maybe add 1
>>> from fp.monads.maybe import Maybe, Just
>>> from fp import p
>>> maybe_int = p(Maybe.catch, int)
>>> string_to_plus = Maybe.arrow_cl(maybe_int, lambda x: Just(x+1))
>>> string_to_plus("1")
Just(2)
>>> string_to_plus("a")
Nothing
Right to Left arrow composition
This expression means the same as: maybe cast the string as an int, then maybe add 1 but read right to left.
>>> from fp.monads.maybe import Maybe
>>> from fp import p
>>> maybe_int = p(Maybe.catch, int)
>>> string_to_plus = Maybe.arrow_cr(lambda x: Maybe(x+1), maybe_int)
>>> string_to_plus("1")
Just(2)
Pass the value of this monad into the action f
Call the action f, throwing away the value of this monad
Execute the function f(*args, **kwargs) and return the value inside the monad.
Catch any errors and call the monad’s fail method
>>> from fp.monads.maybe import Maybe
>>> from fp.monads.either import Either
>>> from fp.monads.iomonad import IO
>>> Maybe.catch(lambda: {'foo': 'bar'}['foo'])
Just('bar')
>>> Maybe.catch(lambda: {}['foo'])
Nothing
>>> Either.catch(lambda: {'foo': 'bar'}['foo'])
Right('bar')
>>> Either.catch(lambda: {}['foo'])
Left(KeyError('foo',))
>>> IO.catch(lambda: {'foo': 'bar'}['foo']).run()
'bar'
>>> IO.catch(lambda: {}['foo']).run()
Traceback (most recent call last):
...
KeyError: 'foo'
Return the failure case of the monad.
>>> from fp.monads.maybe import Maybe
>>> from fp import even, c, p
>>> maybeInt = p(Maybe.catch, int) # convert int to a Maybe arrow
>>> maybeEven = p(Maybe.ap, even) # lift even into Maybe
>>> Maybe.filterM(c(maybeEven, maybeInt), ["x","1","2","3","4"])
Just(['2', '4'])
Map an arrow accross a list of values:
>>> from fp import p
>>> from fp.monads.maybe import Maybe
>>> maybe_int = p(Maybe.catch, int)
>>> Maybe.mapM(maybe_int, ["1", "2"])
Just([1, 2])
>>> Maybe.mapM(maybe_int, ["1", "a"])
Nothing
>>> from fp.monads.iomonad import IO, printLn
>>> action = IO.mapM_(printLn, ["Hello", "World"])
>>> action.run()
Hello
World
>>> action = IO.mapM_(printLn, ["Hello", "World"])
>>> action.run()
Hello
World
The return function for the monad
execute the monadic actions in ms, returning the result
>>> from fp.monads.maybe import Just, Nothing, Maybe
>>> Maybe.sequence([Just(1), Nothing, Just(2)])
Nothing
>>> Maybe.sequence([Just(1), Just(2)])
Just([1, 2])
Execute the
>>> from fp.monads.iomonad import printLn, IO
>>> actions = [printLn("Hello"), printLn("World")]
>>> IO.sequence_(actions).run()
Hello
World
Perform all the actions inside a dictionary and return the result
>>> from fp.monads.maybe import Maybe, Just
>>> Maybe.sequence_dict({"foo": Just(1), "bar": Just(2)}) == Just({'foo': 1, 'bar': 2})
True
Execute the action when False, return a noop otherwise
>>> from fp.monads.iomonad import printLn
>>> _ = printLn("Hello").unless(False).run()
Hello
>>> _ = printLn("Hello").unless(True).run()
Execute the action when True, return a noop otherwise
>>> from fp.monads.iomonad import printLn
>>> _ = printLn("Hello").when(True).run()
Hello
>>> _ = printLn("Hello").when(False).run()
MonadPlus allows a Monad to define what a zero result is and a method for adding two MonadPlus instances together.
>>> from fp.monads.maybe import Maybe
>>> Maybe.guard(True).bind_(lambda: Maybe.ret("Hello"))
Just('Hello')
>>> Maybe.guard(False).bind_(lambda: Maybe.ret("Hello"))
Nothing
Returns Monad.mzero if the pred is False.
>>> from fp.monads.maybe import Just
>>> from fp import even
>>> Just(4).mfilter(even)
Just(4)
>>> Just(3).mfilter(even)
Nothing
An associative operation
Reduces a list of MonadPlus instances using its mplus method:
>>> from fp.monads.maybe import Just, Nothing, Maybe
>>> Maybe.msum([Just(1), Nothing, Just(2)])
Just(1)
>>> Maybe.msum([Nothing, Nothing, Just(2)])
Just(2)
>>> Maybe.msum([Nothing, Nothing, Nothing])
Nothing