Effect-TS - TypeScriptの関数型プログラミング

2024.12.31

Effect-TSとは

Effect-TSは、TypeScriptで関数型プログラミングを実現するライブラリです。型安全なエラーハンドリング、依存性注入、並行処理を提供し、堅牢なアプリケーションを構築できます。

基本概念

Effect型

Effect<A, E, R>は3つの型パラメータを持ちます。

Effect<
  A,  // 成功時の戻り値の型
  E,  // 発生しうるエラーの型
  R   // 必要な依存関係(Requirements)
>

基本的な使い方

Effect の作成

import { Effect } from 'effect';

// 成功するEffect
const success = Effect.succeed(42);

// 失敗するEffect
const failure = Effect.fail(new Error('Something went wrong'));

// 非同期Effect
const asyncEffect = Effect.promise(() => fetch('/api/data').then(r => r.json()));

// 例外をキャッチ
const safe = Effect.try({
  try: () => JSON.parse(invalidJson),
  catch: (error) => new ParseError(error)
});

Effectの実行

import { Effect } from 'effect';

const program = Effect.succeed(42);

// 同期実行
const result = Effect.runSync(program);

// 非同期実行
const promise = Effect.runPromise(program);

// Promise(エラーをEither型で返す)
const exit = await Effect.runPromiseExit(program);

エラーハンドリング

型安全なエラー

import { Effect, Data } from 'effect';

// カスタムエラーの定義
class NetworkError extends Data.TaggedError('NetworkError')<{
  message: string;
}> {}

class ValidationError extends Data.TaggedError('ValidationError')<{
  field: string;
  message: string;
}> {}

// エラーを発生させる可能性のある関数
const fetchUser = (id: string): Effect.Effect<
  User,
  NetworkError | ValidationError
> => {
  if (!id) {
    return Effect.fail(new ValidationError({ field: 'id', message: 'Required' }));
  }
  return Effect.tryPromise({
    try: () => fetch(`/api/users/${id}`).then(r => r.json()),
    catch: () => new NetworkError({ message: 'Failed to fetch' })
  });
};

エラーの処理

const program = fetchUser('123').pipe(
  // 特定のエラーをキャッチ
  Effect.catchTag('NetworkError', (e) =>
    Effect.succeed({ name: 'Fallback User' })
  ),
  // すべてのエラーをキャッチ
  Effect.catchAll((e) => Effect.succeed({ name: 'Default' }))
);

依存性注入

サービスの定義

import { Context, Effect, Layer } from 'effect';

// サービスのインターフェース
class Database extends Context.Tag('Database')<
  Database,
  {
    query: (sql: string) => Effect.Effect<unknown[]>;
  }
>() {}

// サービスを使用する関数
const getUsers = Effect.gen(function* () {
  const db = yield* Database;
  return yield* db.query('SELECT * FROM users');
});

// 実装
const DatabaseLive = Layer.succeed(Database, {
  query: (sql) => Effect.promise(() => pool.query(sql))
});

// テスト用モック
const DatabaseTest = Layer.succeed(Database, {
  query: () => Effect.succeed([{ id: 1, name: 'Test' }])
});

// 実行
const program = getUsers.pipe(
  Effect.provide(DatabaseLive)
);

並行処理

並列実行

import { Effect } from 'effect';

const task1 = Effect.promise(() => fetch('/api/users'));
const task2 = Effect.promise(() => fetch('/api/posts'));
const task3 = Effect.promise(() => fetch('/api/comments'));

// すべてを並列実行
const all = Effect.all([task1, task2, task3], { concurrency: 'unbounded' });

// 並列度を制限
const limited = Effect.all([task1, task2, task3], { concurrency: 2 });

// 最初に完了したものを取得
const race = Effect.race([task1, task2, task3]);

リソース管理

import { Effect } from 'effect';

const acquire = Effect.promise(() => pool.connect());
const release = (conn: Connection) => Effect.promise(() => conn.release());

const withConnection = Effect.acquireUseRelease(
  acquire,
  (conn) => Effect.promise(() => conn.query('SELECT 1')),
  release
);

パイプライン

import { Effect, pipe } from 'effect';

const program = pipe(
  Effect.succeed({ name: 'Alice', age: 30 }),
  Effect.map(user => user.name),
  Effect.flatMap(name => Effect.succeed(`Hello, ${name}!`)),
  Effect.tap(greeting => Effect.log(greeting))
);

// または
const program2 = Effect.succeed({ name: 'Alice', age: 30 }).pipe(
  Effect.map(user => user.name),
  Effect.flatMap(name => Effect.succeed(`Hello, ${name}!`))
);

Generator構文

import { Effect } from 'effect';

const program = Effect.gen(function* () {
  const user = yield* fetchUser('123');
  const posts = yield* fetchPosts(user.id);
  const comments = yield* fetchComments(posts[0].id);

  return { user, posts, comments };
});

スケジューリング

import { Effect, Schedule } from 'effect';

// リトライ戦略
const retry = Schedule.exponential('100 millis').pipe(
  Schedule.compose(Schedule.recurs(3))
);

const program = fetchData.pipe(
  Effect.retry(retry)
);

// 定期実行
const repeated = Effect.repeat(
  fetchData,
  Schedule.spaced('1 minute')
);

まとめ

Effect-TSは、TypeScriptに強力な関数型プログラミング機能を追加します。型安全なエラーハンドリング、依存性注入、並行処理により、保守性と信頼性の高いアプリケーションを構築できます。

← 一覧に戻る