pymonad package

Submodules

pymonad.either module

Implements the Either monad and related functions.

The Either type represents values that can either type A or type B - for any types A and B - but not both at the same time. As a function input type, Either values can be used to define functions which can sensibly deal with multiple types of input; of course in python we don’t need a special way to deal with multiple input types.

Perhaps more usefully, as an output type, Either values can be used to signal that a function may cause an error: Either we get back a useful result or we get back an error message.

When creating Either values directly use the ‘Right’ or ‘Left’ functions:

Example:
>>> x = Right(19)                     # Represents a result value
>>> y = Left('Something went wrong.') # Represents an error value

The ‘insert’ class method is a wrapper around the ‘Right’ function.

Example:
>>> x = Either.insert(9) # Same as Right(9)
class pymonad.either.Either(value, monoid)[source]

Bases: pymonad.monad.Monad

The Either monad class.

amap(monad_value: pymonad.either.Either[M, S]) → pymonad.either.Either[M, T][source]

Applies the function stored in the functor to the value inside ‘functor_value’ returning a new functor value.

bind(kleisli_function: Callable[S, Either[M, T]]) → pymonad.either.Either[M, T][source]

See Monad.bind

classmethod insert(value: T) → pymonad.either.Either[Any, T][source]

See Monad.insert

is_left() → bool[source]

Returns True if this Either instance was created with the ‘Left’ function.

is_right() → bool[source]

Returns True if this Either instance was created with the ‘Right’ function.

map(function: Callable[S, T]) → pymonad.either.Either[M, T][source]

See Monad.map

pymonad.either.Error(value: M) → pymonad.either._Error[M, Any][source]

Creates an error value as the result of a calculation.

pymonad.either.Left(value: M) → pymonad.either.Either[M, Any][source]

Creates a value of the first possible type in the Either monad.

pymonad.either.Result(value: T) → pymonad.either._Error[Any, T][source]

Creates a value representing the successful result of a calculation.

pymonad.either.Right(value: T) → pymonad.either.Either[Any, T][source]

Creates a value of the second possible type in the Either monad.

pymonad.list module

Implements the List monad.

The List monad is frequently used to represent calculations with non-deterministic results, that is: functions which return more than one (possible) result. For example, calculating how chess pieces might move.

Example:
>>> def knight_move(position):
>>>    # calculates a list of every possible square a knight could move
>>>    # to from it's current position
>>>    return ListMonad(position_1, position_2, ..., position_N)
>>> # A list containing every square a knight could reach after 3 moves.
>>> three_moves = (
>>>     List
>>>     .insert(initial_position) # However positions are defined.
>>>     .then(knight_move)
>>>     .then(knight_move)
>>>     .then(knight_move)
>>> )
pymonad.list.ListMonad(*elements) → pymonad.list._List[T][source]

Creates an instance of the List monad.

Args:
elements: any number of elements to be inserted into the list
Returns:
An instance of the List monad.

pymonad.maybe module

Implements the Maybe monad and related functions.

The Maybe monad is used to represent calculations that may or may not return a value. Alternately, if used as function inputs, Maybe values can be used to indicate ‘optional’ inputs, explicitly passing ‘Nothing’ when no input is required.

When creating Maybe values directly use the ‘Just’ function or ‘Nothing’:

Example:
>>> x = Just(19)
>>> y = Just('A string')
>>> z = Nothing

The ‘insert’ class method is a wrapper around the ‘Just’ function.

Example:
>>> x = Maybe.insert(9) # Same as Just(9)
pymonad.maybe.Just(value: T) → pymonad.maybe.Maybe[T][source]

A Maybe object representing the presence of an optional value.

class pymonad.maybe.Maybe(value, monoid)[source]

Bases: pymonad.monad.Monad

The Maybe monad class.

amap(monad_value: pymonad.maybe.Maybe[S]) → pymonad.maybe.Maybe[T][source]

See Monad.amap

bind(kleisli_function: Callable[S, pymonad.maybe.Maybe[T]]) → pymonad.maybe.Maybe[T][source]

See Monad.bind

classmethod insert(value: T) → pymonad.maybe.Maybe[T][source]

See Monad.insert

is_just() → bool[source]

Returns True if the monad instance was created with the ‘Just’ function.

is_nothing() → bool[source]

Returns True if the monad instance is the ‘Nothing’ value.

map(function: Callable[S, T]) → pymonad.maybe.Maybe[T][source]

See Monad.map

class pymonad.maybe.Option(value, monoid)[source]

Bases: pymonad.monad.MonadAlias, pymonad.maybe.Maybe

An alias for the Maybe monad class.

pymonad.maybe.Some(value: T) → pymonad.maybe.Option[T][source]

An Option object representing the presence of an optional value.

pymonad.monad module

Implements the Monad base class.

The Monad base class is an abstract class which defines the operations available on all monad instances. To create a new Monad instance, users should create a class which inherits from Monad and provides implementations for the methods map, amap, bind, and class method insert. See the documentation for those methods for more information on how to implement them properly.

class pymonad.monad.Monad(value, monoid)[source]

Bases: typing.Generic

Represents a “context” in which calculations can be executed.

You won’t create ‘Monad’ instances directly. Instead, sub-classes implement specific contexts. Monads allow you to bind together a series of calculations while maintaining the context of that specific monad.

amap(monad_value: pymonad.monad.Monad[S]) → pymonad.monad.Monad[T][source]

Applies the function stored in the functor to the value inside ‘functor_value’ returning a new functor value.

classmethod apply(function)[source]

Supplies a cleaner interface for applicative functor/amap usage.

Args:
function: A regular function which returns non-monadic values.
Returns:
A monad object based on the input class with the wrapped function and a new method, ‘to_arguments’ which will apply the function.
Example:
>>> @curry(2)
>>> def add(a, b): return a + b
>>>
>>> x = Just(1)
>>> y = Just(2)
>>>
>>> Maybe.apply(add).to_arguments(x, y)  # results in Just(3)
bind(kleisli_function: Callable[S, Monad[T]]) → pymonad.monad.Monad[T][source]

Applies ‘function’ to the result of a previous monadic calculation.

classmethod insert(value: T) → pymonad.monad.Monad[T][source]

Returns an instance of the Functor with ‘value’ in a minimum context.

map(function: Callable[S, T]) → pymonad.monad.Monad[T][source]

Applies ‘function’ to the contents of the functor and returns a new functor value.

then(function: Union[Callable[S, T], Callable[S, Monad[T]]]) → pymonad.monad.Monad[T][source]

Combines the functionality of bind and fmap.

Instead of worrying about whether to use bind or fmap, users can just use the then method to chain function calls together. The then method uses attempts to use bind first and if that doesn’t work, uses fmap instead.

Args:
function: A python function or lambda expression
which returns either a build-in type (int, string, etc.) or an appropriate monad type (Maybe, Either, etc.)
Returns:
A monad value of the same type as ‘self’
class pymonad.monad.MonadAlias(value, monoid)[source]

Bases: pymonad.monad.Monad

Provides monad method overrides which make it easy to give a monad an alias.

MonadAlias provides monad methods which call their base class but then re-cast the result into the alias class. This gives users a mechanism to give monads more semantically meaningful names without needing to re-implement them.

Example:
>>> class Option(MonadAlias, Maybe): # MonadAlias must be the first parent class
>>>     def __repr__(self):
>>>         return f'Some {self.value}' if self.monoid else 'Nothing'
>>> def Some(value):
>>>     return Option(value, True)
>>> # Nothing = Nothing, we don't need to change that one.

Functions which have been previously defined to work with the base type, Maybe in this case, work transparently with the aliased type without needing to be re-written.

Example:
>>> def add_1(x): return Just(x + 1) # Written for the Maybe monad.
>>> opt_val = Option.insert(1).then(add_1) # Result: Some(2)
amap(monad_value)[source]

Applies the function stored in the functor to the value inside ‘functor_value’ returning a new functor value.

bind(kleisli_function)[source]

Applies ‘function’ to the result of a previous monadic calculation.

classmethod insert(value)[source]

Returns an instance of the Functor with ‘value’ in a minimum context.

map(function)[source]

Applies ‘function’ to the contents of the functor and returns a new functor value.

then(function)[source]

Combines the functionality of bind and fmap.

Instead of worrying about whether to use bind or fmap, users can just use the then method to chain function calls together. The then method uses attempts to use bind first and if that doesn’t work, uses fmap instead.

Args:
function: A python function or lambda expression
which returns either a build-in type (int, string, etc.) or an appropriate monad type (Maybe, Either, etc.)
Returns:
A monad value of the same type as ‘self’

pymonad.monoid module

Implements the base Monoid type.

A monoid is an algebraic structure consisting of a set of objects, S, such as integers; strings; etc., and an operation usually denoted as ‘+’ which obeys the following rules:

  1. Closure: If ‘a’ and ‘b’ are in S, then ‘a + b’ is also in S.
  2. Identity: There exists an element in S (denoted 0) such that
    a + 0 = 0 + a = a
  3. Associativity: (a + b) + c = a + (b + c)

For monoid types, the ‘+’ operation is implemented by the method ‘mplus’ and the static method ‘mzero’ is defined to return the identity element of the type.

For example, integers can be monoids in two ways:

  1. mzero = 0 and mplus = addition
  2. mzero = 1 and mplus = multiplication

String can also form a monoid where mzero is the empty string and mplus is concatenation.

class pymonad.monoid.Monoid(value: T)[source]

Bases: typing.Generic

Abstract base class for Monoid instances.

To implement a monoid instance, users should create a sub-class of Monoid and implement the mzero and mplus methods. Additionally, it is the implementers responsibility to ensure that the implementation adheres to the closure, identity and associativity laws for monoids.

mplus(other)[source]

The defining operation of the monoid. This method must be overridden in subclasses and should meet the following conditions:

  1. x + 0 == 0 + x == x
  2. (x + y) + z == x + (y + z) == x + y + z

Where ‘x’, ‘y’, and ‘z’ are monoid values, ‘0’ is the mzero (the identity value) and ‘+’ is mplus.

static mzero() → pymonad.monoid.Monoid[Any][source]

A static method which simply returns the identity value for the monoid type. This method must be overridden in subclasses to create custom monoids. See also: the mzero function.

class pymonad.monoid.ZERO[source]

Bases: object

A generic zero/identity element for monoids.

The ZERO class acts as a constant/singleton with monoid addition implemented on the class itself to always return the other element. It is not actually possible to create an instance of ZERO as calling the constructor simply returns the class itself.

Example:
>>> ZERO == ZERO() # True.
>>> ZERO + 10      # 10
>>> 'hello' + ZERO # 'hello'
pymonad.monoid.mconcat(monoid_list: List[pymonad.monoid.Monoid[T]]) → pymonad.monoid.Monoid[T][source]

Takes a list of monoid values and reduces them to a single value by applying the mplus operation to each all elements of the list.

pymonad.monoid.mzero(monoid_type)[source]

Returns the identity value for monoid_type. Raises TypeError if monoid_type is not a valid monoid.

There are a number of builtin types that can operate as monoids and they can be used as such as is. These “natural” monoids are: int, float, str, and list. While thee mzero method will work on monoids derived from the Monoid class, this mzero function will work for all monoid types, including the “natural” monoids. For this reason it is preferable to call this function rather than calling the mzero method directly unless you know for sure what type of monoid you’re dealing with.

pymonad.promise module

Implements the Promise monad for ordering concurrent computations.

The Promise monad is based on (and named after) Javascript’s Promise objects and function in a similar way. Promises take asynchronous computations and ensure the ordering of execution. In addition to the standard operations on monads, Promises also provide a ‘catch’ method which allows for recovery from errors.

Example:
>>> import asyncio
>>> from pymonad.promise import Promise
>>> from pymonad.tools import curry
>>> @curry(2)
>>> def div(y, x):
>>>    return x / y
>>> async def long_id(x):
>>>     await asyncio.sleep(1)
>>>     return await Promise(lambda resolve, reject: resolve(x))
>>> async def main():
>>>     x = (
>>>         Promise.insert(1)
>>>         .then(long_id)
>>>     )
>>>     y = (
>>>         Promise
>>>         .insert(2)
>>>         .then(long_id)
>>>         .then(div(0)
>>>     )            # Raises an error...
>>>     .catch(lambda error: 2)) # ...which is dealth with here.
>>>     print(
>>>         await Promise.apply(add)
>>>         .to_arguments(x, y)
>>>         .catch(lambda error: 'Recovering...') # This is ignored
>>>                                               # because the previous
>>>                                               # catch already dealt
>>>                                               # with the error.
>>>     )
>>> asyncio.run(main())

The above example will print the value ‘3’ to the screen. The ‘long_id’ coroutine is a stand-in for any async operation that may take some amount of time. When we await the Promise inside the print() call it waits for both arguments to complete before calling ‘add’ with the results. If the first call to ‘catch’ were removed then the error would propagate and be caught by the second call. The program would then print the string ‘Recovering…’ instead of ‘3’.

You can also create a Promise by passing a function directly. This function takes two callbacks as input to signal a successful computation or a failed one.

Example:
>>> import asyncio
>>> def main():
>>>     print(await Promise(lambda resolve, reject: resolve(2)))
>>> asyncio.run(main())

The ‘resolve’ callback can take a value of any type but the ‘reject’ callback should always take an Exception as its argument.

Example:
>>> import asyncio
>>> def main():
>>>     print(await Promise(lambda resolve, reject: reject(IndexError())))
>>> asyncio.run(main())

When run, this program will crash having raised the IndexError without catching it. Similarly, the catch method takes a function which accepts an Exception as it’s input.

Example:
>>> import asyncio
>>> def main():
>>>     print(
>>>         await Promise(lambda resolve, reject: reject(IndexError()))
>>>         .catch(lambda error: print(type(error)))
>>>     )
>>> asyncio.run(main())

This program prints “<type ‘IndexError’>” as its output.

pymonad.promise.Promise(function: Callable[[Callable[S, T], Callable[Exception, T]], T]) → pymonad.promise._Promise[T][source]

Constructs a Promise object for ordering concurrent computations.

Arguments:
function: a function taking two callback typically called
‘resolve’ and ‘reject’. When the computation is successful the value should be returned by calling resolve with the result. If there is an error, call ‘reject’ with an instance of the Exception class.
Returns:
A new Promise object.
Example:
>>> Promise(lambda resolve, reject: resolve('any value'))
>>>   def some_computation(resolve, reject):
>>>       if True:
>>>           return resolve(10)
>>>       else:
>>>           reject(TypeError('Fake error.')) # doesn't need to be returned
>>>   Promise(some_computation)

pymonad.reader module

Implements the Reader monad.

The Reader monad creates a context in which functions have access to an additional read-only input.

pymonad.reader.Compose(function: Callable[R, T]) → pymonad.reader._Reader[R, T][source]

Creates an instance of the Compose monad.

Compose is basically an alias for the Reader monad except with the insert and apply methods removed. It’s purpose is simply to provide a semantically meaningful monad instance to be used specifically for the purpose of function composition.

Example:

def inc(x): return x + 1 def dec(x): return x - 1

convoluted_inc_twice = (Compose(inc)
.then(inc) .then(inc) .then(dec))

convoluted_inc_twice(0) # Result: 2

Technically, ‘convoluted_inc_twice’ is an instance of the Reader monad but since Reader defines the __call__ method, we can treat it just like a function for all intents and purposes. The Compose monad composes functions forward. In the example, the three ‘inc’ operations happen first and then the ‘dec’ and not vice-versa.

pymonad.reader.Pipe(value: T) → pymonad.reader._Pipe[Any, T][source]

Creates an instance of the Pipe monad.

Pipe is basically an alias for the Reader monad except with the insert and apply methods removed. It’s purpose is simply to provide a semantically meaningful monad instance to be used specifically for the purpose of chaining function calls by taking the output of one function as the input to the next.

Since Pipe is a subclass of Reader it’s really building a function but, semantically, pipes start with some input and end with a result. For this reason, Pipe adds a ‘flush’ method which calls the composed function with dummy input (which will be ignored) and simply returns the embedded result. Optionally, you can instead use the unary ‘+’ operator instead of ‘flush’ to do the same thing.

Example:

def inc(x): return x + 1

pipe_with_flush = (Pipe(0)
.then(inc) .then(inc) .flush())
pipe_with_plus = +(Pipe(0)
.then(inc) .then(inc))

pipe_with_flush == pipe_with_plus # True

pymonad.reader.Reader(function: Callable[R, T]) → pymonad.reader._Reader[R, T][source]

Creates an instance of the Reader monad.

Args:
function: a function which takes the read-only data as input and
returns any appropriate type.
Result:
An instance of the Reader monad.

pymonad.state module

Implements the State monad.

pymonad.state.State(state_function: Callable[S, A]) → pymonad.state._State[S, A][source]

The State monad constructor function.

Args:
state_function: a function with type State -> (Any, State)
Returns:
An instance of the State monad.

pymonad.tools module

The tools module contains useful functions that don’t really belong anywhere else.

pymonad.tools.curry(*arguments)

Creates a curried function from a normal function of callable object.

The curry function is itself curried and can be partially applied. It can be used either as a normal function call or as a decorator. The required number_of_arguments parameter makes it possible to curry functions which take a variable number of arguments like the built-in ‘map’ function.

Example:
>>> curried_map = curry(2, map)
>>> @curry(2)
>>> def some_func(x, y, z):
>>>     return x + y - z
Args:
number_of_arguments: The number of arguments function_to_curry takes as input. If function_to_curry takes a variable number of arguments, then number of curried arguments desired in the result: function_to_curry will be called once this many arguments have been supplied. function_to_curry: The function that we wish to curry
Returns:
A new function which may be partially applied simply by passing the desired number of arguments.
pymonad.tools.identity(value: T) → T[source]

Returns it’s input value unchanged.

pymonad.tools.kleisli_compose(function_f: Callable[R, pymonad.monad.Monad[S]], function_g: Callable[S, pymonad.monad.Monad[T]]) → Callable[R, pymonad.monad.Monad[T]][source]

Composes two Kleisli functions.

Kleisli functions are functions which take as input a ‘bare’ value and return an ‘embellished’ value. For instance, if we have a function f which maps a’s to b’s, it’s type is: f :: a -> b

Then the corresponding Kleisli function, f_kleisli has the type: f_kleisli :: a -> (b, m)

The type (b, m) corresponds to the internal representation of the Monad class, so in terms of pymonad, a Kleisli function is one which maps values of type a to values of some sub-class of Monad.

Example:
>>> def fail_if_zero(x):
>>>     return Nothing if x is zero else Just(x)
>>> def add1(x):
>>>     return Just(x + 1)
>>> new_function = kleisli_compose(add1, fail_if_zero)
>>> new_function(0) # returns Just(1)
>>> new_function(-1) # returns Nothing

add1 and fail_if_zero are Kleisli functions and new_function is the function which results from first performing add1 followed by fail_if_zero.

Args:
function_f: a function with type: a -> (b, m) function_g: a function with type: b -> (c, m)
Returns:
A new Kleisli function with type: a -> (c, m)

pymonad.writer module

Implements the Writer monad.

The Writer monad is typically used to append information to a log. The log type is often just strings but can be any type that behaves as a monoid with a defined + (__add__) operator.

Example:
>>> @curry(2)
>>> def add(x, y):
>>>     return Writer(x + y, f"Called function 'add' with arguments {x} and {y}. Result: {x + y}")
>>> @curry(2)
>>> def mul(x, y):
>>>     return Writer(x * y, f"Called function 'mul' with arguments {x} and {y}. Result: {x * y}")
>>> logged_arithmetic = (Writer
>>>                      .insert(0)
>>>                      .then(add(1))
>>>                      .then(mul(2)))
>>> # logged_arithmetic = (2, "Called function 'add' with arguments 1 and 0. Result: 1
>>> #                     Called function 'mul' with arguments 2 and 1. Result: 2")
class pymonad.writer.Writer(value, monoid)[source]

Bases: pymonad.monad.Monad

The Writer monad class.

bind(kleisli_function: Callable[S, Writer[M, T]]) → pymonad.writer.Writer[M, T][source]

See Monad.bind.

classmethod insert(value: T) → pymonad.writer.Writer[M, T][source]

See Monad.insert.

map(function: Callable[S, T]) → pymonad.writer.Writer[M, T][source]

See Monad.map.

Module contents

Top-level package for boxx-pymonad.