--- id: ts-effect-fp-patterns title: Effect / fp-ts — 함수형 에러 / 의존성 / 동시성 category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [typescript, effect, fp-ts, functional, vibe-coding] tech_stack: { language: "TS", applicable_to: ["Backend", "Frontend"] } applied_in: [] aliases: [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. - pipe: 작은 함수 합성. - Layer: 의존성 주입. - Schedule: retry / repeat 정책. ## 💻 코드 패턴 ### Effect 기본 ```ts import { Effect } from 'effect'; const program = Effect.gen(function* () { const user = yield* fetchUser('u1'); // Effect 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 ``` ### Either / 명시적 에러 ```ts import { Effect } from 'effect'; class UserNotFound { readonly _tag = 'UserNotFound'; } class NetworkError { readonly _tag = 'NetworkError'; } function fetchUser(id: string): Effect.Effect { 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) ```ts class Database extends Effect.Service()('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) ```ts 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 ```ts 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 ```ts 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) ```ts 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; const decoded = Schema.decodeUnknownEither(User)(input); ``` ### Resource (자동 cleanup) ```ts 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 가 후속) ```ts 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 ``` ## 🤔 의사결정 기준 | 상황 | 추천 | |---|---| | 작은 / 일반 팀 | 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. ## 🔗 관련 문서 - [[TS_tsconfig_Strategy]] - [[Error_Handling_Result_vs_Throw]]