Not Logged In

PyMonad 1.3

Collection of classes for programming with functors, applicative functors and monads.

PyMonad is a small library
implementing monads and related data abstractions
– functors, applicative functors, and monoids –
for use in implementing functional style programs.
For those familiar with monads in Haskell,
PyMonad aims to implement many of the features you’re used to
so you can use monads in python quickly and easily.
For those who have never used monads but are interested,
PyMonad is an easy way to learn about them in, perhaps,
a slightly more forgiving environment
without needing to learn Haskell.

Features

Easily define curried functions with the @curry decorator.

Straight-forward partial application: just pass a curried function the number of arguments you want.

Functors, Applicative Functors, and Monads

All Monads are also Applicative Functors,
and all Applicative Functors are also Functors,
though the same is not necessarily true in reverse.
All the types included with PyMonad
are defined as all three
but you can define new types however you want.

All of these types ultimately derive from Container
which simply holds a value and provides some basic value equality checking.
The method getValue() will allow you to “extract” the value
of monadic computations if/when necessary.

Functors

All functors define the fmap method
which can be invoked via the fmap operator *.
fmap takes functions which operate on simple types
– integers, strings, etc. –
and allows them to operate of functor types:

Notice that * is also the function composition operator.
In fact,
curried functions are instances of the Reader monad,
and fmap -ing a function over another function
is the same thing as function composition.

Applicative Functors

Functors allow you to use normal functions of a single argument
– like neg above –
with functor types.
Applicative Functors extend that capability
– via amap and its operator & –
allowing you to use normal functions of multiple arguments
with functor types:

Just(9) >> Just(8) # The '9' is thrown away and the result of this computation is 'Just(8)'

Implementing Monads

Implementing other functors, applicatives, or monads is fairly straight-forward.
There are three classes,
serving as interfaces:

Monad --> Applicative --> Functor

To implement a new functor,
create a new class which derives from Functor
and override the fmap method.

To implement a new applicative functor,
create a new class which derives from Applicative
and override the amap and fmap methods.

To implement a new monad,
create a new class which derives from Monad
and override at least the bind method,
and preferably the amap and fmap methods as well.

The operators, *, &, and >>
are pre-defined to call the above methods
so you shouldn’t need to touch them directly.

unit (aka return)

The previous version of pymonad
didn’t include the method unit
(called return in Haskell).
unit takes a bare value,
such as 8,
and places it in a default context for that monad.
Haskell allows polymorphism on return types
as well as supporting type inference,
so you (mostly) don’t have to tell return what types to expect,
it just figures it out.
We can’t do that in Python,
so you always need to tell unit what type you’re expecting.

The unit method is implemented as a class method
in Functor.py, so it can be used with any functor, applicative or monad.
There is also a unitfunction which expects a functor type
(though you can also give it an instance)
and a value
and invokes the corresponding unit method.
It is provided to give a more “functional look” to code,
but use whichever method you prefer.
With the Maybe monad for example:

Maybe.unit(8) # returns Just(8)

unit(Maybe, 8) # also returns Just(8)

In either case all functors (and applicatives and monads) should implement the unit class method.

Monoids

Monoids are a data type
which consists of some operation for combining values of that type,
and an identity value for that operation.
The operation is called mplus
and the identity value is callled mzero.
Despite the names,
they are not necessarily addition and zero.
They can be addition and zero though,
numbers are sort of the typical monoid.

In the case of numbers,
zero is the identity element and addition is the operation.
Monoids adhere to the following laws:

Left and right identity: x + 0 = 0 + x = x

Associativity: (x + y) + z = x + (y + z) = x + y + z

Stings are also monoids with the identity element mzero equal to the empty string,
and the operation mplus concatenation.

Creating New Monoids

To create a new monoids type
create a class deriving from Monoid
and override the mzero static method
which takes no arguments and should return an instance of the class
containing the identity value for the monoid.
Also override the mplus method.
For instance,
numbers can be a monoid in two ways,
one way with zero and addition as discussed above
and the other way with one and multiplication.
We could implement that like this:

The + operator (aka __add__()) is defined to call mplus on monoid instances,
so you can simply “add” monoid values together rather than having to call mplus directly.

“Natural” Monoids

Similar to unit for monads,
there is an mzero function
which expects a type and can be used instead of the mzero method.
Unlike unit however,
the mzero function serves another purpose.
Numbers, strings and lists can all be used as monoids
and all already have an appropriate definition for +.
What they don’t have is an mzero method.
To allow numbers, strings and lists to be used as monoids
without any extra work,
the mzerofunction will return the appropriate value for these types
and will attempt to call the mzero method on anything else.
For instance:

In the definition of add`,
``StringWriter could have also been just Writer.
It’s really only necessary to use subclasses when using unit,
because unit checks for the logType variable.
Otherwise simply giving plain old Writer a string
– or other monoid type argument –
accomplishes the same thing.
Both unit and bind (or >>)
convert *Writer types to plain Writer
but using StringWriter
– or whatever –
makes your intentions more clear.

State Monad

Unlike most of the other monad types,
the state monad doesn’t wrap values
it wraps functions.
Specifically,
it wraps functions which accept a single ‘state’ argument
and produce a result and a new ‘state’ as a 2-tuple.
The ‘state’ can be anything:
simple types like integers,
lists, dictionaries, custom objects/data types,
whatever.
The important thing
is that any given chain of stateful computations
all use the same type of state.

The State constuctor should only be used to create stateful computations.
Trying to use State to inject values,
or even non-stateful functions,
into the monad will cause it to function incorrectly.
To inject values,
use the unit function.

Here’s an example of using State.
We’ll create a little system which can perform addition and subtraction.
Our total will never be allowed to drop below zero.
The state that we’ll be keeping track of is a simple count
of the total number of operations performed.
Every time we perform an addition or subtraction
the count will go up by one:

As mentioned,
The State constructor takes a function which accepts a ‘state’,
in this case simply an integer,
and produces a result and a new state as a tuple.
Although we could have done subtract as a one-liner,
I wanted to show that,
if your computation is more complex than can easily be contained in a lambda expression,
you can use State as a decorator to define the stateful computation.

Using these functions is now simple:

x = unit(State, 1) >> add(2) >> add(3) >> subtract(40) >> add(5)

x now contains a stateful computation but that computation hasn’t been executed yet.
Since State values contain functions,
you can call them like functions
by supplying an initial state value:

y = x(0) # Since we're counting the total number of operations, we start at zero.
print(y) # Prints (5, 4), '5' is the result and '4' is the total number of operations performed.

Calling a State function in this way will always return the (result, state) tuple.
If you’re only interested in the result:

y = x.getResult(0) # Here 'y' takes the value 5, the result of the computataion.

Or if you only care about the final state:

y = x.getState(0) # Here 'y' takes the value 4, the final state of the computation.

Changes

v1.3, 2014-06-28 – Added Monoid instances for List and Maybe, added First and Last Monoids, add State Monad, updated tests for List and Maybe.

Copyright (c) 2014, Jason DeLaat
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.