Effect TS

モナドのdo記法とRustの Option, Result と Effect TS を比較する。

Maybe と Either

  • 無効値をとり得る値: Maybe a, Option<T>
  • 失敗するかもしれない値: Either a b, Result<T, E>
safeDiv :: Int -> Int -> Maybe Int
safeDiv m 0 = Nothing
safeDiv m n = Just (m `div` n)
 
tryDiv :: Int -> Int -> Either () Int
tryDiv m 0 = Left ()
tryDiv m n = Right (m `div` n)
fn safe_div(m: i64, n: i64) -> Option<i64> {
    match n {
        0 => None,
        _ => Some(m / n),
    }
}
 
fn try_div(m: i64, n: i64) -> Result<i64, ()> {
    match n {
        0 => Err(()),
        _ => Ok(m / n),
    }
}

Monadとdo記法

以下のように書き直してネストを減らすことができる。

  • divs1: ネストしている処理
  • divs2: bind (>>=)1{Option,Result}::and_then() を用いる
  • divs3: do記法・? を用いる
divs1 :: Maybe Int
divs1 = case safeDiv 42 2 of
  Nothing -> Nothing
  Just a -> case safeDiv a 0
 
divs2 :: Maybe Int
divs2 =
    safeDiv 42 2 >>= (\a ->
        safeDiv a 0
    )
 
divs3 :: Maybe Int
divs3 = do
  a <- safeDiv 42 2
  safeDiv a 0
fn divs1() -> Option<i64> {
    match safe_div(42, 2) {
        None => None,
        Some(a) => safe_div(a, 0),
    }
}
 
fn divs2() -> Option<i64> {
    safe_div(42, 2)
      .and_then(|a| safe_div(a, 0))
}
 
fn divs3() -> Option<i64> {
    let a = safe_div(42, 2)?;
    safe_div(a, 0)
}

Monad Transformer

  • MaybeEither をそのまま混ぜることはできない
    • → Monad Transformerでモナドを他のモナドに持ち上げて解決するらしい(ミリしら)
  • Rustにはモナドはない2 が、OptionResult は相互に変換できる
fn divs3() -> Option<i64> {
    let a = safe_div(42, 2)?;
    try_div(a, 0).ok() // Result -> Option
}
 
fn divs4() -> Result<i64, ()> {
    let a = try_div(42, 2)?;
    safe_div(a, 0).ok_or(()) // Option -> Result
}

effect systemとは(雑)

実装

Effect TS

  • fp-tsの後継
  • Effect Monad を中心としたライブラリ
  • ScalaのZIOの移植らしい
Effect<number, Error, never>
  • number: 成功時の型
  • Error: 失敗時の型
  • never: 制約
import { Effect, Either, Option } from "npm:effect";
 
const safeDiv = (m: number, n: number) =>
    n === 0 ? Option.none() : Option.some(m / n);
 
const tryDiv = (m: number, n: number) =>
    n === 0 ? Either.left("Division by zero") : Either.right(m / n);
 
// program: Effect.Effect<number, string | NoSuchElementException, never>
const program = Effect.gen(function* () {
    const a = yield* safeDiv(42, 2);
    const b = yield* tryDiv(a, 0);
    return b;
});
 
// { _id: "Exit", _tag: "Failure", cause: { _id: "Cause", _tag: "Fail", failure: "Division by zero" } }
const res = Effect.runSyncExit(program);

TODO

  • 代数的エフェクト(algebraic effect)の実装(Extensible Effectsとか)
  • Effect TS と effect-system がある言語の比較

参考文献

Footnotes

  1. EitherMaybeMonad のインスタンスなので (>>=) :: Monad m => m a -> (a -> m b) -> m b が使える

  2. Rustにeffect-systemを入れるとどうなるかみたいな記事 Extending Rust’s Effect System がある