tags : TypeScript

概要

Christian Ekrem による記事。Effect-TS フレームワークを導入しなくても、TypeScript のネイティブ機能だけで関数型プログラミングのパターンを実現できるという主張。ただし Effect-TS が有効な場面も率直に認めている。

核心的な原則は「型システムに証明を持たせる(make the type system carry the proof)」という parse-don’t-validate 哲学。

問題提起:嘘をつく型シグネチャ

典型的な signupUser 関数のシグネチャ Promise<User> は、実際に起こりうる複数の失敗モードを隠蔽している。バリデーションエラー、メール重複、DB 障害、メール送信サービスのダウンなど、どれも型に反映されていない。

3つのアイデア

アイデア1:誠実なエラー(Honest Errors)

例外を throw するのではなく、エラーを判別共用体(discriminated union)として返す。

type SignupError =
  | { _tag: "InvalidEmail" }
  | { _tag: "EmailTaken"; email: string }
  | { _tag: "DbError"; cause: unknown }
  | { _tag: "EmailServiceDown" };
 
type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };

assertNever() による網羅性チェックで、呼び出し側がすべてのエラーケースを処理することをコンパイラが強制する。

アイデア2:誠実な依存関係(Honest Dependencies)

依存関係をモジュール import の裏に隠すのではなく、関数シグネチャに明示する。

type SignupDeps = {
  readonly findUserByEmail: (email: string) => Promise<Result<User | null, DbError>>;
  readonly createUser: (input: CreateUserInput) => Promise<Result<User, DbError>>;
  readonly sendWelcomeEmail: (email: string) => Promise<Result<void, EmailError>>;
  readonly trackEvent: (name: string, props: Record<string, string>) => Promise<void>;
};

モックフレームワーク不要でテストしやすくなり、関数が実際に何を必要としているかが一目瞭然になる。

アイデア3:合成(Composition)

エラーチェックの繰り返しが冗長になる問題を andThen ヘルパーで解決。

async function andThen<T, U, E1, E2>(
  result: Promise<Result<T, E1>>,
  f: (value: T) => Promise<Result<U, E2>>,
): Promise<Result<U, E1 | E2>>

ただし、4〜5個以上の逐次操作を連ねると、Effect-TS の generator 構文に比べて扱いにくくなる。

素の TypeScript の限界

  • 合成の冗長性: 逐次操作が増えるとネストとボイラープレートが膨張する
  • エラー型の肥大化: モジュール境界をまたぐとエラー共用体が掛け算的に増える
  • 構造化された並行処理: TypeScript の Promise では、Effect の Fiber モデルが提供する安全性保証が得られない

筆者の見積もりでは、これらのパターンで日常の約80%はカバーできるが、残り20%のケースでは Effect-TS の複雑さを受け入れる価値がある。

実践的な提言

フレームワークを採用する前にこれらのパターンを手作業で学ぶことを推奨。ライブラリが「なぜ」存在するかを理解することが、便利さ以上に価値がある。

Source

effect-ts