โข Option
โข Result
โข The ? operator propagates errors up the call stack concisely
โข Combinators like .map(), .and_then(), .unwrap_or() chain fallible operations
โข The compiler forces you to handle every error case โ no silent failures
โข Option
โข Result
โข The ? operator propagates errors up the call stack concisely
โข Combinators like .map(), .and_then(), .unwrap_or() chain fallible operations
โข The compiler forces you to handle every error case โ no silent failures
// 056: Result as Monad
// Chain fallible operations with and_then and ?
use std::num::ParseIntError;
#[derive(Debug, PartialEq)]
enum CalcError {
Parse(String),
DivByZero,
}
fn parse_int(s: &str) -> Result<i32, CalcError> {
s.parse::<i32>().map_err(|e| CalcError::Parse(e.to_string()))
}
fn safe_div(a: i32, b: i32) -> Result<i32, CalcError> {
if b == 0 { Err(CalcError::DivByZero) } else { Ok(a / b) }
}
// Approach 1: Using and_then (monadic bind)
fn compute_bind(s1: &str, s2: &str) -> Result<i32, CalcError> {
parse_int(s1).and_then(|a| {
parse_int(s2).and_then(|b| safe_div(a, b))
})
}
// Approach 2: Using ? operator (syntactic sugar for bind)
fn compute_question(s1: &str, s2: &str) -> Result<i32, CalcError> {
let a = parse_int(s1)?;
let b = parse_int(s2)?;
safe_div(a, b)
}
// Approach 3: Chained pipeline
fn pipeline(s: &str) -> Result<i32, CalcError> {
parse_int(s)
.and_then(|n| safe_div(n, 2))
.map(|n| n + 1)
.map(|n| n * 2)
}
fn main() {
println!("compute_bind(10, 3) = {:?}", compute_bind("10", "3"));
println!("compute_question(10, 0) = {:?}", compute_question("10", "0"));
println!("pipeline(10) = {:?}", pipeline("10"));
println!("pipeline(abc) = {:?}", pipeline("abc"));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_int() {
assert_eq!(parse_int("42"), Ok(42));
assert!(parse_int("abc").is_err());
}
#[test]
fn test_compute_bind() {
assert_eq!(compute_bind("10", "3"), Ok(3));
assert_eq!(compute_bind("10", "0"), Err(CalcError::DivByZero));
assert!(compute_bind("abc", "3").is_err());
}
#[test]
fn test_compute_question() {
assert_eq!(compute_question("10", "3"), Ok(3));
assert_eq!(compute_question("10", "0"), Err(CalcError::DivByZero));
}
#[test]
fn test_pipeline() {
assert_eq!(pipeline("10"), Ok(12));
assert!(pipeline("abc").is_err());
}
}
(* 056: Result as Monad *)
(* Chain fallible operations with bind *)
(* Approach 1: Explicit pattern matching *)
let parse_int s =
match int_of_string_opt s with
| Some n -> Ok n
| None -> Error (Printf.sprintf "Not a number: %s" s)
let safe_div a b =
if b = 0 then Error "Division by zero"
else Ok (a / b)
let compute_explicit s1 s2 =
match parse_int s1 with
| Error e -> Error e
| Ok a ->
match parse_int s2 with
| Error e -> Error e
| Ok b -> safe_div a b
(* Approach 2: Using Result.bind *)
let compute_bind s1 s2 =
parse_int s1
|> Result.bind (fun a ->
parse_int s2
|> Result.bind (fun b ->
safe_div a b))
(* Approach 3: Pipeline with map and bind *)
let add_one r = Result.map (fun x -> x + 1) r
let double r = Result.map (fun x -> x * 2) r
let pipeline s =
parse_int s
|> Result.bind (fun n -> safe_div n 2)
|> Result.map (fun n -> n + 1)
|> Result.map (fun n -> n * 2)
(* Tests *)
let () =
assert (parse_int "42" = Ok 42);
assert (parse_int "abc" = Error "Not a number: abc");
assert (compute_explicit "10" "3" = Ok 3);
assert (compute_explicit "10" "0" = Error "Division by zero");
assert (compute_explicit "abc" "3" = Error "Not a number: abc");
assert (compute_bind "10" "3" = Ok 3);
assert (compute_bind "10" "0" = Error "Division by zero");
assert (pipeline "10" = Ok 12);
assert (pipeline "abc" = Error "Not a number: abc");
Printf.printf "โ All tests passed\n"
A monad is a type with `return` (wrap a value) and `bind` (chain operations that may fail). Result is a monad: `Ok` is return, `and_then`/`bind` chains fallible operations, short-circuiting on `Err`.
| Operation | OCaml | Rust |
|---|---|---|
| Return/wrap | `Ok x` | `Ok(x)` |
| Bind | `Result.bind r f` | `r.and_then(f)` |
| Map | `Result.map f r` | `r.map(f)` |
| Sugar | `let* x = r in ...` | `let x = r?;` |
| Short-circuit | Pattern match | `?` operator |