Files
2nd/10_Wiki/Topics/Coding/TS_Effect_FP_Patterns.md
T
2026-05-09 21:08:02 +09:00

4.9 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
ts-effect-fp-patterns Effect / fp-ts — 함수형 에러 / 의존성 / 동시성 Coding draft B conceptual 2026-05-09 2026-05-09
typescript
effect
fp-ts
functional
vibe-coding
language applicable_to
TS
Backend
Frontend
Effect
fp-ts
Either
Option
Result
Layer
dependency injection
retry

Effect / fp-ts

Promise + try/catch 의 한계 = 명시적 에러 type X, 의존성 묶임. Effect (modern) / fp-ts (전통) = 타입 안전 에러 + DI + 재시도 + 동시성 일급. 학습 곡선 큼 — 팀 합의 필요.

📖 핵심 개념

  • Effect<A, E, R>: 성공 A / 에러 E / 의존성 R.
  • pipe: 작은 함수 합성.
  • Layer: 의존성 주입.
  • Schedule: retry / repeat 정책.

💻 코드 패턴

Effect 기본

import { Effect } from 'effect';

const program = Effect.gen(function* () {
  const user = yield* fetchUser('u1'); // Effect<User, ApiError, never>
  const orders = yield* fetchOrders(user.id);
  return { user, orders };
});

const result = await Effect.runPromise(program); // throws on failure
// 또는
const result = await Effect.runPromiseExit(program); // Exit<A, E>

Either / 명시적 에러

import { Effect } from 'effect';

class UserNotFound { readonly _tag = 'UserNotFound'; }
class NetworkError { readonly _tag = 'NetworkError'; }

function fetchUser(id: string): Effect.Effect<User, UserNotFound | NetworkError> {
  return Effect.tryPromise({
    try: () => api.get(id),
    catch: (e) => e instanceof TypeError ? new NetworkError() : new UserNotFound(),
  });
}

// Match
const handled = program.pipe(
  Effect.catchTag('UserNotFound', () => Effect.succeed(defaultUser)),
  Effect.catchTag('NetworkError', () => Effect.retry(...)),
);

Layer (DI)

class Database extends Effect.Service<Database>()('Database', {
  effect: Effect.gen(function* () {
    const url = yield* Config.string('DATABASE_URL');
    return { query: (sql: string) => Effect.tryPromise(() => pg.query(sql)) };
  }),
}) {}

const program = Effect.gen(function* () {
  const db = yield* Database;
  const r = yield* db.query('SELECT 1');
  return r;
});

// 실행 시 layer 제공
Effect.runPromise(program.pipe(Effect.provide(Database.Default)));

Schedule (retry + backoff)

import { Schedule, Effect } from 'effect';

const policy = Schedule.exponential('100 millis').pipe(
  Schedule.compose(Schedule.recurs(5)),
  Schedule.jittered,
);

const robust = program.pipe(Effect.retry(policy));

Concurrency

const all = Effect.all([fetchA, fetchB, fetchC], { concurrency: 'unbounded' });
const limited = Effect.all(items.map(process), { concurrency: 10 });

// Race
const fast = Effect.race(slowEndpoint, fastEndpoint);

Stream

import { Stream } from 'effect';

const numbers = Stream.range(1, 1000);
const processed = numbers.pipe(
  Stream.map(n => n * 2),
  Stream.filter(n => n % 3 === 0),
  Stream.take(10),
  Stream.runCollect,
);

Schema (built-in 검증, like zod)

import { Schema } from 'effect';

const User = Schema.Struct({
  id: Schema.String.pipe(Schema.uuid()),
  email: Schema.String.pipe(Schema.email()),
  age: Schema.Number.pipe(Schema.greaterThan(0)),
});

type User = Schema.Schema.Type<typeof User>;

const decoded = Schema.decodeUnknownEither(User)(input);

Resource (자동 cleanup)

const file = Effect.acquireRelease(
  Effect.sync(() => fs.openSync(path, 'r')),
  (fd) => Effect.sync(() => fs.closeSync(fd)),
);

const program = Effect.scoped(Effect.gen(function* () {
  const fd = yield* file;
  // 사용
  // 자동 close
}));

fp-ts (전통 — Effect 가 후속)

import { pipe } from 'fp-ts/function';
import * as TE from 'fp-ts/TaskEither';

const program = pipe(
  fetchUser('u1'),
  TE.flatMap(user => fetchOrders(user.id)),
  TE.map(orders => orders.length),
);

const result = await program(); // Either<Error, number>

🤔 의사결정 기준

상황 추천
작은 / 일반 팀 Promise + try/catch + zod
큰 백엔드 + FP 팀 Effect
Monad / FP 학습 fp-ts
Error type 안전 강 Effect
큰 동시성 / 재시도 정책 복잡 Effect
Front + Back 공유 Effect (range 광범위)

안티패턴

  • Promise 와 Effect 혼용: 변환 비용. 한 모델만.
  • Effect 도입 — 팀 학습 X: 유지보수 못 함.
  • catchAll 모든 에러 swallow: type 안전 의미 없음.
  • runSync prod: error 던짐. runPromise / runFork.
  • Layer 없이 직접 의존: testability 낮음.
  • Stream 으로 작은 데이터: overkill. 일반 array.

🤖 LLM 활용 힌트

  • 도입 결정 = 팀 합의 필수.
  • Effect 가 modern (2024+), fp-ts 는 legacy (저자가 Effect 로 이동).
  • Schema + DI + retry 가 가장 ROI.

🔗 관련 문서