d9e5668cec
Fixes and improvements from exhaustive audit: Consistency fixes in SKILL.md: - 'Pipe & Filter' → 'Pipe and Filter' (one stray '&' found and corrected) - 'Singleton for factory instance' → clarified to 'register factory as singleton-scoped via DI container' (less misleading wording) - Documentation Format section updated with note that SKILL.md itself is the authoritative source for related-pattern combinations Coverage fix — Related Patterns sections: - Added '## Related Patterns' to ALL 94 pattern files (was 0/94) - Each section lists 3–6 related patterns with relationship descriptions - Covers: why they're related, when to prefer one vs the other, and which are often confused SOLID principles → Creational → Structural → Behavioral → Architectural → Concurrency → Functional → Resilience → Data Access → Messaging → Testing → Error Handling → Microservice — all 13 categories covered Code verification: - Python: 0 failures (all 85 testable blocks pass) - Go: 0 failures (all 76 testable blocks pass) - JavaScript: 0 failures (all 78 testable blocks pass) - All 239 code blocks verified correct after edits Final skill state: - 108 files, 36,524 lines across 13 reference categories - 94/94 pattern files have Related Patterns sections - 2,815-line SKILL.md with 67 decision trees, 23 scenarios, 0 broken references, 0 naming inconsistencies
10 KiB
10 KiB
Monad Pattern
Problem
Operations can fail (return null, throw exceptions) or produce optional values. Chaining such operations leads to deeply nested null checks or try/catch blocks, obscuring the actual logic.
Solution
Wrap values in a monadic context (like Maybe/Option or Result/Either) that carries success or failure information. Provide bind/flatMap/then operations that chain computations, automatically propagating the failure case without explicit checks at each step.
When to Use
- Chaining operations where any step may fail
- Replacing null checks with a type-safe alternative
- Modeling computations with context (errors, async, optional values)
- When you want explicit error handling without exceptions
When to Avoid
- Simple code where null checks are trivial and clear
- Languages with robust exception handling when exceptions are preferred
- When the team finds monadic abstractions confusing (adoption cost)
Pseudocode
// Maybe monad
Maybe.of(value) // wraps value in Just
Maybe.nothing() // represents absence
just(5).map(x => x * 2) // Just(10)
nothing().map(x => x * 2) // Nothing
just(5).flatMap(x => just(x + 1)) // Just(6)
just(5).flatMap(x => nothing()) // Nothing
// Result monad
Ok(value).map(fn) // Ok(fn(value))
Err(error).map(fn) // Err(error) -- fn is not called
Ok(5).flatMap(x => Ok(x * 2)) // Ok(10)
Ok(5).flatMap(x => Err("fail")) // Err("fail")
Python
from __future__ import annotations
from typing import TypeVar, Generic, Callable, Optional
T = TypeVar("T")
U = TypeVar("U")
E = TypeVar("E")
# --- Maybe Monad ---
class Maybe(Generic[T]):
def __init__(self, value: Optional[T], is_nothing: bool = False):
self._value = value
self._is_nothing = is_nothing
@staticmethod
def just(value: T) -> Maybe[T]:
return Maybe(value, is_nothing=False)
@staticmethod
def nothing() -> Maybe:
return Maybe(None, is_nothing=True)
def map(self, fn: Callable[[T], U]) -> Maybe[U]:
if self._is_nothing:
return Maybe.nothing()
return Maybe.just(fn(self._value))
def flat_map(self, fn: Callable[[T], Maybe[U]]) -> Maybe[U]:
if self._is_nothing:
return Maybe.nothing()
return fn(self._value)
def get_or_else(self, default: T) -> T:
return default if self._is_nothing else self._value
def __repr__(self) -> str:
return "Nothing" if self._is_nothing else f"Just({self._value})"
# --- Result Monad ---
class Result(Generic[T, E]):
def __init__(self, value: Optional[T], error: Optional[E], is_err: bool):
self._value = value
self._error = error
self._is_err = is_err
@staticmethod
def ok(value: T) -> Result[T, E]:
return Result(value, None, is_err=False)
@staticmethod
def err(error: E) -> Result[T, E]:
return Result(None, error, is_err=True)
def map(self, fn: Callable[[T], U]) -> Result[U, E]:
if self._is_err:
return Result.err(self._error)
return Result.ok(fn(self._value))
def flat_map(self, fn: Callable[[T], Result[U, E]]) -> Result[U, E]:
if self._is_err:
return Result.err(self._error)
return fn(self._value)
def get_or_else(self, default: T) -> T:
return default if self._is_err else self._value
def __repr__(self) -> str:
return f"Err({self._error})" if self._is_err else f"Ok({self._value})"
def main() -> None:
# Maybe examples
print("=== Maybe Monad ===")
val = Maybe.just(5).map(lambda x: x * 2).map(lambda x: x + 1)
print(f"Just(5) -> *2 -> +1 = {val}")
empty = Maybe.nothing().map(lambda x: x * 2)
print(f"Nothing -> *2 = {empty}")
chained = Maybe.just(10).flat_map(
lambda x: Maybe.just(x // 2) if x > 0 else Maybe.nothing()
)
print(f"Just(10) -> flatMap(//2) = {chained}")
print(f"Get or else: {Maybe.nothing().get_or_else(42)}")
# Result examples
print("\n=== Result Monad ===")
def safe_divide(a: float, b: float) -> Result:
if b == 0:
return Result.err("Division by zero")
return Result.ok(a / b)
r1 = safe_divide(10, 2).map(lambda x: x * 3)
print(f"10/2 * 3 = {r1}")
r2 = safe_divide(10, 0).map(lambda x: x * 3)
print(f"10/0 * 3 = {r2}")
# Chain results
r3 = safe_divide(100, 5).flat_map(lambda x: safe_divide(x, 4))
print(f"100/5 then /4 = {r3}")
r4 = safe_divide(100, 0).flat_map(lambda x: safe_divide(x, 4))
print(f"100/0 then /4 = {r4}")
if __name__ == "__main__":
main()
Go
package main
import "fmt"
// --- Maybe ---
type Maybe[T any] struct {
value T
isNothing bool
}
func Just[T any](v T) Maybe[T] {
return Maybe[T]{value: v, isNothing: false}
}
func Nothing[T any]() Maybe[T] {
return Maybe[T]{isNothing: true}
}
func MapMaybe[T, U any](m Maybe[T], fn func(T) U) Maybe[U] {
if m.isNothing {
return Nothing[U]()
}
return Just(fn(m.value))
}
func FlatMapMaybe[T, U any](m Maybe[T], fn func(T) Maybe[U]) Maybe[U] {
if m.isNothing {
return Nothing[U]()
}
return fn(m.value)
}
func (m Maybe[T]) GetOrElse(def T) T {
if m.isNothing {
return def
}
return m.value
}
func (m Maybe[T]) String() string {
if m.isNothing {
return "Nothing"
}
return fmt.Sprintf("Just(%v)", m.value)
}
// --- Result ---
type Result[T any] struct {
value T
err string
isErr bool
}
func Ok[T any](v T) Result[T] {
return Result[T]{value: v, isErr: false}
}
func Err[T any](e string) Result[T] {
return Result[T]{err: e, isErr: true}
}
func MapResult[T, U any](r Result[T], fn func(T) U) Result[U] {
if r.isErr {
return Err[U](r.err)
}
return Ok(fn(r.value))
}
func FlatMapResult[T, U any](r Result[T], fn func(T) Result[U]) Result[U] {
if r.isErr {
return Err[U](r.err)
}
return fn(r.value)
}
func (r Result[T]) String() string {
if r.isErr {
return fmt.Sprintf("Err(%s)", r.err)
}
return fmt.Sprintf("Ok(%v)", r.value)
}
func safeDivide(a, b float64) Result[float64] {
if b == 0 {
return Err[float64]("division by zero")
}
return Ok(a / b)
}
func main() {
// Maybe examples
fmt.Println("=== Maybe Monad ===")
val := MapMaybe(MapMaybe(Just(5), func(x int) int { return x * 2 }), func(x int) int { return x + 1 })
fmt.Printf("Just(5) -> *2 -> +1 = %s\n", val)
empty := MapMaybe(Nothing[int](), func(x int) int { return x * 2 })
fmt.Printf("Nothing -> *2 = %s\n", empty)
fmt.Printf("Get or else: %d\n", Nothing[int]().GetOrElse(42))
// Result examples
fmt.Println("\n=== Result Monad ===")
r1 := MapResult(safeDivide(10, 2), func(x float64) float64 { return x * 3 })
fmt.Printf("10/2 * 3 = %s\n", r1)
r2 := MapResult(safeDivide(10, 0), func(x float64) float64 { return x * 3 })
fmt.Printf("10/0 * 3 = %s\n", r2)
r3 := FlatMapResult(safeDivide(100, 5), func(x float64) Result[float64] { return safeDivide(x, 4) })
fmt.Printf("100/5 then /4 = %s\n", r3)
r4 := FlatMapResult(safeDivide(100, 0), func(x float64) Result[float64] { return safeDivide(x, 4) })
fmt.Printf("100/0 then /4 = %s\n", r4)
}
JavaScript
// --- Maybe Monad ---
class Maybe {
constructor(value) {
this._value = value;
this._isNothing = value === null || value === undefined;
}
static just(value) {
return new Maybe(value);
}
static nothing() {
return new Maybe(null);
}
map(fn) {
return this._isNothing ? Maybe.nothing() : Maybe.just(fn(this._value));
}
flatMap(fn) {
return this._isNothing ? Maybe.nothing() : fn(this._value);
}
getOrElse(defaultValue) {
return this._isNothing ? defaultValue : this._value;
}
toString() {
return this._isNothing ? "Nothing" : `Just(${this._value})`;
}
}
// --- Result Monad ---
class Result {
constructor(value, error, isErr) {
this._value = value;
this._error = error;
this._isErr = isErr;
}
static ok(value) {
return new Result(value, null, false);
}
static err(error) {
return new Result(null, error, true);
}
map(fn) {
return this._isErr ? Result.err(this._error) : Result.ok(fn(this._value));
}
flatMap(fn) {
return this._isErr ? Result.err(this._error) : fn(this._value);
}
getOrElse(defaultValue) {
return this._isErr ? defaultValue : this._value;
}
toString() {
return this._isErr ? `Err(${this._error})` : `Ok(${this._value})`;
}
}
function safeDivide(a, b) {
return b === 0 ? Result.err("Division by zero") : Result.ok(a / b);
}
function main() {
// Maybe examples
console.log("=== Maybe Monad ===");
const val = Maybe.just(5)
.map((x) => x * 2)
.map((x) => x + 1);
console.log(`Just(5) -> *2 -> +1 = ${val}`);
const empty = Maybe.nothing().map((x) => x * 2);
console.log(`Nothing -> *2 = ${empty}`);
const chained = Maybe.just(10).flatMap((x) =>
x > 0 ? Maybe.just(Math.floor(x / 2)) : Maybe.nothing()
);
console.log(`Just(10) -> flatMap(//2) = ${chained}`);
console.log(`Get or else: ${Maybe.nothing().getOrElse(42)}`);
// Result examples
console.log("\n=== Result Monad ===");
const r1 = safeDivide(10, 2).map((x) => x * 3);
console.log(`10/2 * 3 = ${r1}`);
const r2 = safeDivide(10, 0).map((x) => x * 3);
console.log(`10/0 * 3 = ${r2}`);
const r3 = safeDivide(100, 5).flatMap((x) => safeDivide(x, 4));
console.log(`100/5 then /4 = ${r3}`);
const r4 = safeDivide(100, 0).flatMap((x) => safeDivide(x, 4));
console.log(`100/0 then /4 = ${r4}`);
}
main();
Related Patterns
- Result Type — Result/Either is the most common Monad in application code. Monad is the theoretical foundation; Result Type is the practical application.
- Null Object — Both avoid null checks. Monad (Maybe/Option) chains computations that may return nothing; Null Object provides a do-nothing default.
- Chain of Responsibility — Monadic chaining is conceptually similar: each step processes if the previous succeeded.
- Guard Clause — Guard Clause at function entry + Result Type as return type is idiomatic fail-fast with Monadic propagation.
- Higher-Order Functions — Monad's
mapandflatMapare higher-order functions that apply transformations inside the context.