This was going to be a functor and monad tutorial in various programming languages using the same post structure. While working on the Rust version of the post, I came to the realization that learning exactly what they are (either in the context of category theory or Haskell) is not really useful. So I scrapped the idea and decided to do a quick showcase of some examples of the Functor and Monad patterns in the Rust standard library.
Functor
In Rust, the functor pattern resembles a generic Functor<T> with a practical implementation of the following function:
fn <, , >(: Functor<>, : ) -> Functor<>
where
: () -> ,
{
!()
}
In English:
If I have a
FunctorofT, and I have a function that turnsTs intoUs, the functionfmapgives me aFunctorofU.
Example 1: Option
Option with Option::map is a functor.
impl<> <> {
pub fn <, >(, : ) -> <>
where
: () -> ,
{
match {
() => (()),
=> ,
}
}
}
Given an Option<T> and a mapping function FnOnce(T) -> U, the method map returns an Option<U>:
fn () {
// Option<i32> => Fn(i32) -> String => Option<String>
// Some(i32) => i32 -> String => Some(i32)
let : <> = (5);
let : <> = .(|| .());
!(, (::("5")));
// Option<i32> => Fn(i32) -> String => Option<String>
// None => None
let : <> = ;
let : <> = .(|| .());
!(, );
}
Example 2: Result
Result is generic over two types: a success type S and error type E. Therefore, any potential functor needs to map either S or E, while the other type can be restricted as needed for a practical implementation of fmap.
Mapping The Success Type S
Result with Result::map is a functor, mapping the success type S.
impl<, > <, > {
pub fn <>(, : F) -> <, >
where
F: () -> ,
{
match {
() => (()),
() => (),
}
}
}
Given a Result<T, E> and a mapping function FnOnce(T) -> U, the method map returns a Result<U, E>.
fn () {
// Result<i32, &str> => Fn(i32) -> String => Result<String, &str>
// Ok(i32) => i32 -> String => Ok(i32)
let : <, &> = (5);
let : <, &> = .(|| .());
!(, (::("5")));
// Result<i32, &str> => Fn(i32) -> String => Result<String, &str>
// Err(&str) => Err(&str)
let : <, &> = ("invalid integer");
let : <, &> = .(|| .());
!(, ("invalid integer"));
}
Mapping The Error Type E
Result with Result::map_err is a functor, mapping the error type E.
impl<, > <, > {
pub fn <>(, : F) -> <, >
where
F: () -> ,
{
match {
() => (),
() => (()),
}
}
}
Given a Result<S, T> and a mapping function FnOnce(T) -> U, the method map_err returns a Result<S, U>.
fn () {
// Result<i32, &str> => Fn(&str) -> bool => Result<i32, bool>
// Ok(i32) => Ok(i32)
let : <, &> = (5);
let : <, > = .(|| .() > 5);
!(, (5));
// Result<i32, &str> => Fn(&str) -> bool => Result<i32, bool>
// Err(&str) => &str -> bool => Err(bool)
let : <, &> = ("invalid integer");
let : <, > = .(|| .() > 5);
!(, (true));
}
Example 3: [T N]
[T N] with <[T N]>::map is a functor.
impl<, const : > [; ] {
pub fn <, >(, : ) -> [; ]
where
: () -> ,
{
.(NeverShortCircuit::wrap_mut_1(f)).0
}
}
Given an array [T N] and a mapping function FnMut(T) -> U, the method map returns an array [U N].
fn () {
// [i32; 5] => Fn(i32) -> bool => [bool; 5]
let : [; 5] = [1, 2, 3, 4, 5];
let : [; 5] = .(|| >= 3);
!(, [false, false, true, true, true]);
}
Example 4: impl Iterator
Any implementation of Iterator with Iterator::map is a functor. The implementation is generic over the associated type Iterator::item.
trait {
type ;
fn <, >(, : ) -> Map<, >
where
::Sized,
: (::) -> ,
{
Map::new(, f)
}
}
Notably, Map<Self, F> implements the following:
impl<, , > for Map<, >
where
: ,
: (< as >::) -> ,
type = U;
Given an impl Iterator<Item = T> and a mapping function FnMut(T) -> U, the method map returns an impl Iterator<Item = U>.
fn () {
// impl Iterator<Item = u32> => Fn(u32) -> bool
// => impl Iterator<Item = bool>
let : &mut dyn < = > = &mut [1, 2, 3, 4].();
let : &mut dyn < = > = &mut .(|| < 10);
for in {
!();
}
}
Monad
In Rust, the monad pattern resembles a generic Monad<T> with a practical implementation of the following function:
fn <, , >(: Monad<>, : ) -> Monad<>
where
: () -> Monad<>,
{
!()
}
In English:
If I have a
MonadofT, and I have a function that turnsTs into "MonadofU"s, the functionbindgives me aMonadofU.
Example 1: Option
Option with Option::and_then is a monad.
impl<> <> {
pub fn <, >(, : ) -> <>
where
: () -> <>,
{
match {
() => (),
=> ,
}
}
}
Given an Option<T> and a mapping function FnOnce(T) -> Option<U>, the method map returns an Option<U>.
// Taken from the standard library example:
// Fn(u32) -> Option<String>
fn (: ) -> <> {
.().(|| .())
}
fn () {
// Option<u32> => Fn(u32) -> Option<String> => Option<String>
// Some(u32) => u32 -> Some(String) => Some(String)
let : <> = (2);
let : <> = .();
!(, (::("4")));
// Option<u32> => Fn(u32) -> Option<String> => Option<String>
// Some(u32) => u32 -> None => None
let : <> = (1_000_000);
let : <> = .();
!(, );
// Option<u32> => Fn(u32) -> Option<String> => Option<String>
// None => None
let : <> = ;
let : <> = .();
!(, );
}
Example 2: Result
Result is generic over two types: a success type S and error type E. Therefore, any potential monad needs to map either S or E, while the other type can be restricted as needed for a practical implementation of bind.
In the case of Result, there isn't many practical use for mapping the error type to another Result<T, E>
Mapping The Success Type S
Result with Result::and_then is a monad, mapping the success type S.
impl<, > <, > {
pub fn <>(, : F) -> <, >
where
F: () -> <, >,
{
match {
() => (),
() => (),
}
}
}
Given a Result<T, E> and a mapping function FnOnce(T) -> Result<U>, the method and_then returns a Result<U, E>.
// Taken from the standard library example:
// Fn(u32) -> Result<String, &str>
fn (: ) -> <, & > {
.().(|| .()).("overflowed")
}
fn () {
// Result<u32, &str> => Fn(u32) -> Result<String, &str> => Result<String, &str>
// Ok(u32) => u32 -> Some(String) => Ok(String)
let : <, &> = (2);
let : <, &> = .();
!(, (::("4")));
// Result<u32, &str> => Fn(u32) -> Result<String, &str> => Result<String, &str>
// Ok(u32) => u32 -> Err(&str) => Err(&str)
let : <, &> = (1_000_000);
let : <, &> = .();
!(, ("overflowed"));
// Result<u32, &str> => Fn(u32) -> Result<String, &str> => Result<String, &str>
// Err(&str) => Err(&str)
let : <, &> = ("not a number");
let : <, &> = .();
!(, ("not a number"));
}
Mapping The Error Type E
This case does not provide many practical uses for it to warrant inclusion in the standard library.
Given a
Result<S, T>and a mapping functionFnOnce(T) -> Result<S, U>, the method returns aResult<S, U>.
This would mean the mapping function on the error type can possibly produce an Ok(S) with a value of the success type, essentially producing the value out of thin air or from the error itself.
Example 3: impl Iterator
Any implementation of Iterator with Iterator::flat_map is a monad. The implementation is generic over the associated type Iterator::item.
trait {
type ;
fn <, >(, : ) -> FlatMap<, , >
where
::Sized,
: ,
: (::) -> ,
{
FlatMap::new(, f)
}
}
Notably, FlatMap<Self, U, F> implements the following:
impl<, , > for FlatMap<, , >
where
: ,
: ,
: (< as >::) -> ,
type = <U as >::;
Given an impl Iterator<Item = T> and a mapping function FnMut(T) -> impl Iterator<Item = U>, the method flat_map returns an impl Iterator<Item = U>.
// Fn(u32) -> impl Iterator<Item = u32>
fn (: ) -> impl < = > {
0..=
}
fn () {
// impl Iterator<Item = u32> => Fn(u32) -> impl Iterator<Item = u32>
// => impl Iterator<Item = u32>
let : &mut dyn < = > = &mut [1, 2, 3].();
let : &mut dyn < = > = &mut .();
!(
.().::<<>>(),
![0, 1, 0, 1, 2, 0, 1, 2, 3]
);
}
Conclusion
At the end of the day, although the function signatures and implementations seem complicated the intended outcomes are simple:
Functor
Functor<T> + (T) -> U = Functor<U>Monad
Monad<T> + (T) -> Monad<U> = Monad<U>