Python でモナド的パターンの実装(Maybe, Either)

関数型プログラミングの Maybe(Option)と Either(Result)パターンを Python で実装すると、エラー処理を型安全かつ合成可能にできます。

Maybe パターン

値が存在するかしないかを表現します。

from typing import TypeVar, Generic, Callable, Optional
from dataclasses import dataclass

T = TypeVar("T")
U = TypeVar("U")

class Maybe(Generic[T]):
    pass

@dataclass
class Just(Maybe[T]):
    value: T

@dataclass
class Nothing(Maybe[T]):
    pass

def map_maybe(m: Maybe[T], f: Callable[[T], U]) -> Maybe[U]:
    if isinstance(m, Just):
        return Just(f(m.value))
    return Nothing()

def bind_maybe(m: Maybe[T], f: Callable[[T], Maybe[U]]) -> Maybe[U]:
    if isinstance(m, Just):
        return f(m.value)
    return Nothing()

使用例:

def safe_div(a: int, b: int) -> Maybe[float]:
    if b == 0:
        return Nothing()
    return Just(a / b)

result = bind_maybe(Just(10), lambda x: safe_div(x, 2))
# Just(value=5.0)

Either パターン

成功と失敗の両方に値を持てます。

from typing import TypeVar, Generic, Callable, Union
from dataclasses import dataclass

L = TypeVar("L")  # Left(エラー)
R = TypeVar("R")  # Right(成功)

class Either(Generic[L, R]):
    pass

@dataclass
class Left(Either[L, R]):
    value: L

@dataclass
class Right(Either[L, R]):
    value: R

def map_either(e: Either[L, R], f: Callable[[R], U]) -> Either[L, U]:
    if isinstance(e, Right):
        return Right(f(e.value))
    return e

def bind_either(
    e: Either[L, R], f: Callable[[R], Either[L, U]]
) -> Either[L, U]:
    if isinstance(e, Right):
        return f(e.value)
    return e

使用例:

def parse_int(s: str) -> Either[str, int]:
    try:
        return Right(int(s))
    except ValueError:
        return Left(f"Cannot parse '{s}'")

result = bind_either(
    parse_int("42"),
    lambda x: Right(x * 2) if x > 0 else Left("Must be positive")
)
# Right(value=84)

これらのパターンにより、None チェックの連鎖や try-except の乱用を避け、エラー処理を宣言的に記述できます。