071: Identity Monad
Difficulty: โญโญ Level: Intermediate A monad that does nothing โ stripped bare so you can see the structure clearly.The Problem This Solves
When you learn about monads through `Option` and `Result`, the monad machinery is mixed together with the interesting behavior (short-circuiting on `None`, carrying error types). It's hard to tell what's "the monad part" and what's "the `Option` part." Beginners often ask: "So what exactly does a monad add, independent of any specific effect?" It's a reasonable question. The answer is buried inside types that have a lot going on. The Identity monad is a monad that does nothing extra. No short-circuiting. No error carrying. No effects whatsoever. It wraps a value, lets you chain operations on it, and unwraps cleanly. The only thing it provides is the monadic structure itself. This sounds useless โ and for production code, it mostly is. But for understanding, it's exactly what you need: the monad skeleton with nothing obscuring it. And in advanced Rust, it becomes useful as a base case for monad transformers (stacking multiple effects together). The Identity monad exists to solve exactly that pain: it makes the abstract concrete by removing everything except the pattern itself.The Intuition
Imagine a box. You put a value in. You can apply functions to the value inside. You can chain those functions. At the end, you take the value out. That's it. The box doesn't do anything else. It doesn't skip steps. It doesn't carry errors. It's just a box that supports chaining.put in โ transform โ transform โ transform โ take out
Identity::of(10) โ double โ add_one โ to_string โ .run()
Compare to `Option`: the box might be empty, and if it's empty, all transforms are skipped. Compare to `Result`: the box might contain an error, and transforms are skipped. `Identity`: the box always has a value, transforms always run.
The minimal interface every monad has:
1. `of(value)` โ put a value into the monad ("return" or "pure" in theory)
2. `bind(f)` โ apply a function that returns a wrapped value, flatten the result ("bind" or `>>=`)
3. `run()` โ extract the final value (only possible for Identity since there's no effect to unwrap)
How It Works in Rust
The struct โ as simple as possible#[derive(Debug, Clone, PartialEq)]
struct Identity<A>(A); // just a newtype wrapper
The three core operations
impl<A> Identity<A> {
// "return" / "pure" โ put a value into Identity
fn of(x: A) -> Self {
Identity(x)
}
// "bind" / ">>=" โ apply f, get back Identity<B>
// f must return Identity<B>, not plain B
// This is what distinguishes bind from map
fn bind<B, F: FnOnce(A) -> Identity<B>>(self, f: F) -> Identity<B> {
f(self.0) // unwrap, apply, return whatever f returned
}
// "map" โ apply a plain function (not monadic)
fn map<B, F: FnOnce(A) -> B>(self, f: F) -> Identity<B> {
Identity(f(self.0)) // unwrap, apply, re-wrap
}
// extract the final value
fn run(self) -> A {
self.0
}
}
Chaining operations
let result = Identity::of(10)
.bind(|x| Identity::of(x * 2)) // 10 โ 20, wrapped in Identity
.bind(|x| Identity::of(x + 1)) // 20 โ 21, wrapped in Identity
.run(); // unwrap: 21
assert_eq!(result, 21);
`bind` vs `map` โ the key distinction
// map: the function returns a plain value โ Identity wraps it
Identity(5).map(|x| x * 2) // Identity(10)
// bind: the function returns Identity โ no double-wrapping
Identity(5).bind(|x| Identity::of(x * 2)) // Identity(10), not Identity(Identity(10))
`bind` flattens the result. That flattening is the essence of "monad" โ you can compose functions that each return a wrapped value, and the wrapping doesn't accumulate.
The laws hold for Identity too
// Left Identity: Identity::of(a).bind(f) == f(a)
let f = |x: i32| Identity::of(x * 3);
assert_eq!(Identity::of(5).bind(f), f(5)); // Identity(15) == Identity(15)
// Right Identity: m.bind(Identity::of) == m
let m = Identity(42);
assert_eq!(m.clone().bind(Identity::of), m); // Identity(42) == Identity(42)
These feel trivially true for Identity โ because there are no effects to get in the way. That's the point: Identity shows you the laws in their purest form.
What This Unlocks
- Monad transformer base case. In advanced Rust, you can build `OptionT`, `ResultT` and other "stacked monad" types by transforming the Identity monad. Identity is the plain base you add effects on top of.
- Understanding `bind` vs `map`. The difference becomes obvious with Identity stripped of distractions: `map` lifts a plain function; `bind` chains functions that themselves produce wrapped values. Once you see it here, you'll recognize it in `Option`, `Result`, `Iterator`, and `Future`.
- Testable monad laws. Identity is the easiest monad to verify laws against โ no edge cases like `None` or `Err`. Use it to validate your understanding of the three laws before applying them to more complex types.
Key Differences
| Concept | OCaml | Rust |
|---|---|---|
| Type definition | `type 'a t = Identity of 'a` | `struct Identity<A>(A)` (newtype) |
| `return` / `of` | `let return x = Identity x` | `fn of(x: A) -> Self` |
| `bind` | `let bind (Identity x) f = f x` | `fn bind<B, F>(self, f: F) -> Identity<B>` |
| Extraction | `let run (Identity x) = x` | `fn run(self) -> A` |
| Use in practice | Common as monad transformer base | Rare directly; useful for understanding |
// Identity monad โ the simplest possible monad.
// Wraps a value with zero extra effects.
// Useful as a base case in monad transformers.
#[derive(Debug, Clone, PartialEq)]
struct Identity<A>(A);
impl<A> Identity<A> {
/// monadic `return` / `pure` โ lift a value into Identity
fn of(x: A) -> Self {
Identity(x)
}
/// `bind` (>>=) โ sequence computations
fn bind<B, F: FnOnce(A) -> Identity<B>>(self, f: F) -> Identity<B> {
f(self.0)
}
/// Functor `map`
fn map<B, F: FnOnce(A) -> B>(self, f: F) -> Identity<B> {
Identity(f(self.0))
}
/// Extract the wrapped value
fn run(self) -> A {
self.0
}
}
fn main() {
// Functor law: map id = id
let v = Identity(42);
assert_eq!(v.clone().map(|x| x), v);
println!("Identity monad laws hold");
// Monad: sequence computations
let result = Identity::of(10)
.bind(|x| Identity::of(x * 2))
.bind(|x| Identity::of(x + 1));
assert_eq!(result.run(), 21);
println!("chain: {}", Identity::of(10)
.bind(|x| Identity::of(x * 2))
.bind(|x| Identity::of(x + 1))
.run());
// Demonstrate map
let doubled = Identity(21).map(|x| x * 2);
println!("mapped: {}", doubled.run());
// Works with any type, not just integers
let s = Identity("hello")
.map(|s| s.to_uppercase())
.bind(|s| Identity::of(s.len()));
println!("string chain: {}", s.run());
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_functor_identity_law() {
// map id = id
let v = Identity(42);
assert_eq!(v.clone().map(|x| x), v);
}
#[test]
fn test_bind_chain() {
let result = Identity::of(10)
.bind(|x| Identity::of(x * 2))
.bind(|x| Identity::of(x + 1));
assert_eq!(result.run(), 21);
}
#[test]
fn test_monad_left_identity() {
// left identity: return a >>= f = f a
let f = |x: i32| Identity::of(x * 3);
let lhs = Identity::of(5).bind(f);
let rhs = f(5);
assert_eq!(lhs, rhs);
}
#[test]
fn test_monad_right_identity() {
// right identity: m >>= return = m
let m = Identity(42);
let result = m.clone().bind(Identity::of);
assert_eq!(result, m);
}
#[test]
fn test_map_composition_law() {
// map (f . g) = map f . map g
let v = Identity(5);
let f = |x: i32| x + 1;
let g = |x: i32| x * 2;
let lhs = v.clone().map(|x| f(g(x)));
let rhs = v.map(g).map(f);
assert_eq!(lhs, rhs);
}
}
(* Identity monad โ the simplest possible monad.
Wraps a value with zero extra effects.
Useful as a base case in monad transformers. *)
type 'a t = Identity of 'a
let return x = Identity x
let bind (Identity x) f = f x
let (>>=) m f = bind m f
let map f (Identity x) = Identity (f x)
let run (Identity x) = x
(* Functor law: map id = id *)
let () =
let v = Identity 42 in
assert (map (fun x -> x) v = v);
Printf.printf "Identity monad laws hold\n"
(* Monad: sequence computations *)
let () =
let result =
return 10
>>= (fun x -> return (x * 2))
>>= (fun x -> return (x + 1))
in
assert (run result = 21);
Printf.printf "chain: %d\n" (run result)