The Reader Monad

The Reader monad pass the state you want to share between functions. Functions may read that state, but can't change it. The reader monad lets us access shared immutable state within a monadic context. In the Reader monad this shared state is called the environment.

The Reader is just a fancy name for a wrapped function, so this monad could also be called the Function monad, or perhaps the Callable monad. Reader is all about composing wrapped functions.

This IPython notebook uses the OSlash library for Python 3.4, aka Ø. You can install Ø using:

> pip3 install oslash

In [106]:
from oslash import Reader
unit = Reader.unit

A Reader wraps a function, so it takes a callable:


In [107]:
r = Reader(lambda name: "Hi %s!" % name)

In Python you can call this wrapped function as any other callable:


In [108]:
r("Dag")


Out[108]:
'Hi Dag!'

Unit

Unit is a constructor that takes a value and returns a Reader that ignores the environment. That is it ignores any value that is passed to the Reader when it's called:


In [109]:
r = unit(42)
r("Ignored")


Out[109]:
42

Bind

You can bind a Reader to a monadic function using the pipe | operator (The bind operator is called >>= in Haskell). A monadic function is a function that takes a value and returns a monad, and in this case it returns a new Reader monad:


In [117]:
r = Reader(lambda name: "Hi %s!" % name)

b = r | (lambda x: unit(x.replace("Hi", "Hello")))
b("Dag")


Out[117]:
'Hello Dag!'

Applicative

Apply (*) is a beefed up map. It takes a Reader that has a function in it and another Reader, and extracts that function from the first Reader and then maps it over the second one (basically composes the two functions).


In [122]:
r = Reader(lambda name: "Hi %s!" % name)

a = Reader.pure(lambda x: x + "!!!") * r
a("Dag")


Out[122]:
'Hi Dag!!!!'

MonadReader

The MonadReader class provides a number of convenience functions that are very useful when working with a Reader monad.


In [112]:
from oslash import MonadReader
asks = MonadReader.asks
ask = MonadReader.ask

Ask

Provides a way to easily access the environment. Ask lets us read the environment and then play with it:


In [113]:
r = ask() | (lambda x: unit("Hi %s!" % x))
r("Dag")


Out[113]:
'Hi Dag!'

Asks

Given a function it returns a Reader which evaluates that function and returns the result.


In [114]:
r = asks(len)
r("banana")


Out[114]:
6

A Longer Example

This example has been translated to Python from https://gist.github.com/egonSchiele/5752172.


In [115]:
from oslash import Reader, MonadReader
ask = MonadReader.ask
 
def hello():
    return ask() | (lambda name: 
           unit("Hello, " + name + "!"))
 
def bye():
    return ask() | (lambda name: 
           unit("Bye, " + name + "!"))
 
def convo():
    return hello() | (lambda c1: 
           bye() | (lambda c2: 
           unit("%s %s" % (c1, c2))))

r = convo()
print(r("dag"))


Hello, dag! Bye, dag!

That is it, that's the Reader monad for you in Python and Ø!


In [ ]: