Using the monad pattern

A monad defines return and bind operations for a type. The return operation is like a constructor to make the monad. The bind operation incorporates new information and returns a new monad. There are also several laws that monads should obey. Rather than quote the laws, we'll just say that monads should behave well when daisy chained like the following:

MyMonad::return(value)  //We start with a new MyMonad<A>
.bind(|x| x+x) //We take a step into MyMonad<B>
.bind(|y| y*y); //Similarly we get to MyMonad<C>

In Rust, there are several semi-monads that appear in standard libraries:

fn main()
{
let v1 = Some(2).and_then(|x| Some(x+x)).and_then(|y| Some(y*y));
println!("{:?}", v1);

let v2 = None.or_else(|| None).or_else(|| Some(222));
println!("{:?}", v2);
}

In this example, the normal Option constructors, Some or None, take the place of the monadic naming convention, return. There are two semi-monads implemented here, one associated with and_then, and the other with or_else. Both of these correspond to the monadic bind naming convention for the operator responsible for incorporating new information into a new monad return value.

Monadic bind operations are also polymorphic, meaning they should permit returning monads of different types from the current monad. According to this rule, or_else is not technically a monad; hence it is a semi-monad:

fn main() {
let v3 = Some(2).and_then(|x| Some("abc"));
println!("{:?}", v3);

// or_else is not quite a monad
// does not permit polymorphic bind
//let v4 = Some(2).or_else(|| Some("abc"));
//println!("{:?}", v4);
}

Monads were originally developed to express side-effects in purely functional languages. Isn't that a contradiction—pure with side-effects?

The answer is no if the effects are passed as input and output through pure functions. However, for this to work, every function would need to declare every state variable and pass it along, which could become a huge list of parameters. This is where monads come in. A monad can hide state inside itself, which becomes essentially a larger, more complex function than what the programmer interacts with.

One concrete example of side-effect hiding is the concept of a universal logger. The monadic return and bind can be used to wrap state and computation inside of a monad that will log all intermediate results. Here is the logger monad:

use std::fmt::{Debug};

struct LogMonad<T>(T);
impl<T> LogMonad<T> {
fn _return(t: T) -> LogMonad<T>
where T: Debug {
println!("{:?}", t);
LogMonad(t)
}
fn bind<R,F>(&self, f: F) -> LogMonad<R>
where F: FnOnce(&T) -> R,
R: Debug {
let r = f(&self.0);
println!("{:?}", r);
LogMonad(r)
}
}

fn main() {
LogMonad::_return(4)
.bind(|x| x+x)
.bind(|y| y*y)
.bind(|z| format!("{}{}{}", z, z, z));
}

As long as each result implements the Debug trait, it can be automatically logged with this pattern.

The monad pattern is also very useful for chaining together code that can't be written in a normal code block. For example, code blocks are always evaluated eagerly. If you want to define code that will be evaluated later or in pieces, the lazy monad pattern is very convenient. Lazy evaluation is a term used to describe code or data that is not evaluated until it is referenced. This is contrary to the typical eager evaluation of Rust code that will execute immediately regardless of context. Here is the lazy monad pattern:

struct LazyMonad<A,B>(Box<Fn(A) -> B>);

impl<A: 'static,B: 'static> LazyMonad<A,B> {
fn _return(u: A) -> LazyMonad<B,B> {
LazyMonad(Box::new(move |b: B| b))
}
fn bind<C,G: 'static>(self, g: G) -> LazyMonad<A,C>
where G: Fn(B) -> C {
LazyMonad(Box::new(move |a: A| g(self.0(a))))
}
fn apply(self, a: A) -> B {
self.0(a)
}
}

fn main() {
let notyet = LazyMonad::_return(()) //we create LazyMonad<()>
.bind(|x| x+2) //and now a LazyMonad<A>
.bind(|y| y*3) //and now a LazyMonad<B>
.bind(|z| format!("{}{}", z, z));

let nowdoit = notyet.apply(222); //The above code now run
println!("nowdoit {}", nowdoit);
}

This block defines statements that will be evaluated one at a time after a value is supplied, but not before. This may seem a bit trivial since we can do the same with a simple closure and code block; however, to make this pattern stick, let's consider a more complex case—an asynchronous web server.

A web server will typically receive a full HTTP request before processing it. Choosing what to do with a request is sometimes called routing. Then requests are sent to a request handler. In the following code, we define a server that helps us wrap routes and handlers into a single web server object. Here are the type and method definitions:

use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;

struct ServerMonad<St> {
state: St,
handlers: Vec<Box<Fn(&mut St,&String) -> Option<String>>>
}

impl<St: Clone> ServerMonad<St> {
fn _return(st: St) -> ServerMonad<St> {
ServerMonad {
state: st,
handlers: Vec::new()
}
}
fn listen(&mut self, address: &str) {
let listener = TcpListener::bind(address).unwrap();
for stream in listener.incoming() {
let mut st = self.state.clone();
let mut buffer = [0; 2048];
let mut tcp = stream.unwrap();
tcp.read(&mut buffer);
let buffer = String::from_utf8_lossy(&buffer).into_owned();
for h in self.handlers.iter() {
if let Some(response) = h(&mut st,&buffer) {
tcp.write(response.as_bytes());
break
}
}
}
}
fn bind_handler<F>(mut self, f: F) -> Self
where F: 'static + Fn(&mut St,&String) -> Option<String> {
self.handlers.push(Box::new(f));
self
}
}

This type defines return and bind like operations. However, the bind function is not polymorphic and the operation is not a pure function. Without these compromises, we would need to fight against the Rust type and ownership system; the preceding example is not written monadically due to complications when trying to box and copy closures. This is an expected trade-off and the semi-monad pattern should not be discouraged when appropriate.

To define our web server responses, we can attach handlers like in the following code:

fn main() {
ServerMonad::_return(())
.bind_handler(|&mut st, ref msg| if msg.len()%2 == 0 { Some("divisible by 2".to_string()) } else { None })
.bind_handler(|&mut st, ref msg| if msg.len()%3 == 0 { Some("divisible by 3".to_string()) } else { None })
.bind_handler(|&mut st, ref msg| if msg.len()%5 == 0 { Some("divisible by 5".to_string()) } else { None })
.bind_handler(|&mut st, ref msg| if msg.len()%7 == 0 { Some("divisible by 7".to_string()) } else { None })
.listen("127.0.0.1:8888");
}

If you run this program and send messages to localhost 8888, then you may get a response if the message length is divisible by 2, 3, 5, or 7.