TS 제네릭 (Result 모나드)
비공개 문항 — 학습 오염 방지. 측정 영상에서 응답 원문과 함께 본문 공개.
기대 포맷타입 정의 + 사용 예 + 한계
사전 정의 함정2건 — 발동 모델에 ⚠ 표시
모델별 응답 · 채점
32개 모델. 점수만 보임 — 모델명 클릭 시 raw·근거·cross 펼침.
Claude Opus 4.8Anthropic100100806010094
아래는 `strict` 모드(특히 `strictFunctionTypes`, `exactOptionalPropertyTypes`)를 켠 TypeScript 5.9 기준으로 동작하는 구현입니다.
## 핵심 설계 포인트
- `Result<T, E>`는 `OkResult` / `ErrResult` **클래스의 union**으로 정의 → discriminated union(`kind`) + 메서드 둘 다 확보
- 메서드 시그니처를 **두 클래스에서 동일**하게 맞춰서 union 위에서 메서드 호출/람다 추론이 깨지지 않게 함
- `E`가 콜백 파라미터(반공변 위치)에 나타나지만, **클래스 메서드는 bivariant 검사**라서 `Result<U, E2>`를 `Result<U, E | E2>`에 대입하는 게 통과됨 → 내부 캐스팅 불필요
```ts
// ============================================================
// 1) Result<T, E> discriminated union
// ============================================================
export type Result<T, E> = OkResult<T, E> | ErrResult<T, E>;
abstract class ResultBase<T, E> {
abstract readonly kind: "ok" | "err";
// 2) 메서드들 (시그니처를 양쪽 클래스에서 동일하게 유지)
abstract map<U>(f: (value: T) => U): Result<U, E>;
abstract mapErr<F>(f: (error: E) => F): Result<T, F>;
abstract andThen<U, E2>(f: (value: T) => Result<U, E2>): Result<U, E | E2>;
abstract orElse<U, F>(f: (error: E) => Result<U, F>): Result<T | U, F>;
abstract unwrapOr(fallback: T): T;
isOk(): this is OkResult<T, E> {
return this.kind === "ok";
}
isErr(): this is ErrResult<T, E> {
return this.kind === "err";
}
}
export class OkResult<T, E> extends ResultBase<T, E> {
readonly kind = "ok" as const;
constructor(readonly value: T) {
super();
}
map<U>(f: (value: T) => U): Result<U, E> {
return new OkResult<U, E>(f(this.value));
}
mapErr<F>(_f: (error: E) => F): Result<T, F> {
return new OkResult<T, F>(this.value);
}
// 4) andThen: 다음 단계의 E2가 기존 E에 union으로 누적됨
andThen<U, E2>(f: (value: T) => Result<U, E2>): Result<U, E | E2> {
return f(this.value);
}
orElse<U, F>(_f: (error: E) => Result<U, F>): Result<T | U, F> {
return new OkResult<T | U, F>(this.value);
}
unwrapOr(_fallback: T): T {
return this.value;
}
}
export class ErrResult<T, E> extends ResultBase<T, E> {
readonly kind = "err" as const;
constructor(readonly error: E) {
super();
}
map<U>(_f: (value: T) => U): Result<U, E> {
return new ErrResult<U, E>(this.error);
}
mapErr<F>(f: (error: E) => F): Result<T, F> {
return new ErrResult<T, F>(f(this.error));
}
andThen<U, E2>(_f: (value: T) => Result<U, E2>): Result<U, E | E2> {
return new ErrResult<U, E | E2>(this.error);
}
orElse<U, F>(f: (error: E) => Result<U, F>): Result<T | U, F> {
return f(this.error);
}
unwrapOr(fallback: T): T {
return fallback;
}
}
// 생성자 헬퍼 (E/T 기본값을 never로 둬서 추론이 좁게 시작되게 함)
export const ok = <T, E = never>(value: T): Result<T, E> =>
new OkResult<T, E>(value);
export const err = <E, T = never>(error: E): Result<T, E> =>
new ErrResult<T, E>(error);
```
## AsyncResult (Promise<Result> 체이닝)
```ts
// ============================================================
// 3) AsyncResult<T, E> : Promise<Result<T, E>> 래퍼
// ============================================================
export class AsyncResult<T, E> implements PromiseLike<Result<T, E>> {
constructor(private readonly promise: Promise<Result<T, E>>) {}
static from<T, E>(p: Promise<Result<T, E>>): AsyncResult<T, E> {
return new AsyncResult(p);
}
/** throw 가능한 Promise<T>를 AsyncResult로 흡수 */
static tryCatch<T, E>(
p: Promise<T>,
onError: (e: unknown) => E,
): AsyncResult<T, E> {
return new AsyncResult<T, E>(
p.then((v) => ok<T, E>(v)).catch((e) => err<E, T>(onError(e))),
);
}
map<U>(f: (value: T) => U): AsyncResult<U, E> {
return new AsyncResult(this.promise.then((r) => r.map(f)));
}
mapErr<F>(f: (error: E) => F): AsyncResult<T, F> {
return new AsyncResult(this.promise.then((r) => r.mapErr(f)));
}
// 4) 비동기에서도 E2가 union으로 누적
andThen<U, E2>(
f: (value: T) => AsyncResult<U, E2> | Result<U, E2>,
): AsyncResult<U, E | E2> {
return new AsyncResult<U, E | E2>(
this.promise.then((r) => {
if (r.isErr()) return err<E | E2, U>(r.error);
const next = f(r.value);
return next instanceof AsyncResult ? next.promise : next;
}),
);
}
orElse<U, F>(
f: (error: E) => AsyncResult<U, F> | Result<U, F>,
): AsyncResult<T | U, F> {
return new AsyncResult<T | U, F>(
this.promise.then((r) => {
if (r.isOk()) return ok<T | U, F>(r.value);
const next = f(r.error);
return next instanceof AsyncResult ? next.promise : next;
}),
);
}
async unwrapOr(fallback: T): Promise<T> {
return (await this.promise).unwrapOr(fallback);
}
// await 가능하게 PromiseLike 구현
then<R1 = Result<T, E>, R2 = never>(
onfulfilled?: ((v: Result<T, E>) => R1 | PromiseLike<R1>) | null,
onrejected?: ((reason: unknown) => R2 | PromiseLike<R2>) | null,
): PromiseLike<R1 | R2> {
return this.promise.then(onfulfilled, onrejected);
}
}
```
## infer / conditional type 유틸 + `all`
```ts
// ============================================================
// infer 기반 추출 + 튜플 결합(error union 자동 누적)
// ============================================================
export type InferOk<R> = R extends Result<infer T, infer _E> ? T : never;
export type InferErr<R> = R extends Result<infer _T, infer E> ? E : never;
type OkTuple<T extends readonly Result<unknown, unknown>[]> = {
-readonly [K in keyof T]: T[K] extends Result<infer U, unknown> ? U : never;
};
// 매핑된 타입을 인덱싱해서 union 생성 (naked 분배 대신 안전하게)
type ErrUnion<T extends readonly Result<unknown, unknown>[]> = {
[K in keyof T]: T[K] extends Result<unknown, infer E> ? E : never;
}[number];
export function all<const T extends readonly Result<unknown, unknown>[]>(
results: T,
): Result<OkTuple<T>, ErrUnion<T>> {
const values: unknown[] = [];
for (const r of results) {
if (r.isErr()) return err<ErrUnion<T>, OkTuple<T>>(r.error as ErrUnion<T>);
values.push(r.value);
}
return ok<OkTuple<T>, ErrUnion<T>>(values as OkTuple<T>);
}
```
## 사용 예 3가지
```ts
// --- 공통 에러 타입 ---
type ParseErr = { tag: "ParseErr"; input: string };
type RangeErr = { tag: "RangeErr"; value: number };
type HttpErr = { tag: "HttpErr"; status: number };
type NotFound = { tag: "NotFound"; id: string };
const toInt = (s: string): Result<number, ParseErr> => {
const n = Number(s);
return Number.isInteger(n) ? ok(n) : err({ tag: "ParseErr", input: s });
};
const positive = (n: number): Result<number, RangeErr> =>
n > 0 ? ok(n) : err({ tag: "RangeErr", value: n });
```
### 예 1) 동기 검증 파이프라인 — error 타입 자동 누적
```ts
const r1 = toInt("42").andThen(positive);
// ^? Result<number, ParseErr | RangeErr> ← 두 에러가 union으로 합쳐짐
const out = r1.map((n) => n * 2).unwrapOr(0); // number
```
### 예 2) 비동기 체이닝 (AsyncResult + 동기 Result 혼합)
```ts
const fetchUser = (id: string): AsyncResult<{ id: string; name: string }, HttpErr> =>
AsyncResult.tryCatch(
fetch(`/users/${id}`).then((res) => res.json() as Promise<{ id: string; name: string }>),
(): HttpErr => ({ tag: "HttpErr", status: 500 }),
);
const ensureName = (u: { id: string; name: string }): Result<string, NotFound> =>
u.name ? ok(u.name) : err({ tag: "NotFound", id: u.id });
async function demo() {
const name = await fetchUser("7")
.andThen(ensureName) // AsyncResult<string, HttpErr | NotFound>
.map((n) => n.toUpperCase())
.unwrapOr("ANONYMOUS"); // Promise<string>
return name;
}
```
### 예 3) 여러 Result 결합 (`all`)
```ts
const combined = all([toInt("1"), toInt("2"), positive(3)]);
// ^? Result<[number, number, number], ParseErr | RangeErr>
const sum = combined.map(([a, b, c]) => a + b + c).unwrapOr(-1);
```
## Result 패턴이 부적합한 케이스 1가지
**여러 값을 시간에 걸쳐 방출하는 스트림/이벤트**, 그리고 **복구 불가능한 프로그래밍 버그(불변식 위반)** 에는 `Result`가 맞지 않습니다.
```ts
// ❌ 부적합 예: 스트림은 "단일 결과"가 아니라 N개의 값을 낸다.
// Result<T, E>는 성공 1개 / 실패 1개만 표현 가능 → 모델 불일치
async function* readLines(): AsyncIterable<Result<string, never>> { /* ... */ }
// ❌ 부적합 예: 호출자가 절대 복구할 수 없는 내부 불변식 위반
function assertNever(x: never): never {
// 여기 도달 = 코드 버그. Result로 감싸 봤자 호출자가 처리할 의미 있는 분기가 없다.
throw new Error(`Unreachable: ${JSON.stringify(x)}`);
}
```
이유:
- **스트림**: `Result`는 "0/1개의 성공 + 단일 에러"만 표현하므로, 다회 방출/부분 실패/백프레셔를 가진 스트림은 `AsyncIterable` / Observable 류로 모델링해야 함.
- **불변식 위반**: 호출자가 의미 있게 분기·복구할 수 없는 버그는 `Result`로 감싸면 오히려 에러 union만 오염시키고 처리 강제만 늘어남 → `throw`(fail-fast)가 적합. `Result`는 *예상 가능하고 호출자가 다룰 수 있는* 도메인 에러에만 쓰는 게 좋습니다.채점 근거class union(OkResult/ErrResult kind)+map/mapErr/andThen/orElse/unwrapOr. AsyncResult PromiseLike+tryCatch+then. ★andThen E|E2 union 누적(^? 타입주석 표시). infer/conditional(InferOk/InferErr/ErrUnion mapped+indexed)·ok<T,E=never>. bonus `all` const combinator. ★bivariance 설계 통찰(클래스 메서드 bivariant→캐스팅 불필요). 부적합=스트림+불변식위반(assertNever). neverthrow/fp-ts 인용 빠짐(peer 동일 gap). 4-7 SOTA(94) 동급.
GPT-5.5OpenAI808080608078
아래 코드는 `strict` 기준으로 쓸 수 있는 Rust 스타일 `Result<T, E>` + `AsyncResult<T, E>` 구현 예시입니다.
핵심은 `ok: true | false` discriminated union이고, `andThen`을 체이닝하면 `E | F` 형태로 에러 타입이 자동 누적됩니다.
```ts
/* result.ts */
export type Result<T, E> = Ok<T, E> | Err<T, E>;
export type Ok<T, E = never> = {
readonly ok: true;
readonly value: T;
} & ResultMethods<T, E>;
export type Err<T = never, E = never> = {
readonly ok: false;
readonly error: E;
} & ResultMethods<T, E>;
export interface ResultMethods<T, E> {
map<U>(fn: (value: T) => U): Result<U, E>;
mapErr<F>(fn: (error: E) => F): Result<T, F>;
andThen<U, F>(fn: (value: T) => Result<U, F>): Result<U, E | F>;
orElse<U, F>(fn: (error: E) => Result<U, F>): Result<T | U, F>;
unwrapOr<U>(defaultValue: U): T | U;
}
const widenError = <T, E, Extra>(result: Result<T, E>): Result<T, E | Extra> =>
result as Result<T, E | Extra>;
const widenOk = <T, E, Extra>(result: Result<T, E>): Result<T | Extra, E> =>
result as Result<T | Extra, E>;
const makeOk = <T, E = never>(value: T): Result<T, E> => ({
ok: true,
value,
map<U>(fn: (value: T) => U): Result<U, E> {
return makeOk<U, E>(fn(value));
},
mapErr<F>(_fn: (error: E) => F): Result<T, F> {
return makeOk<T, F>(value);
},
andThen<U, F>(fn: (value: T) => Result<U, F>): Result<U, E | F> {
return widenError<U, F, E>(fn(value));
},
orElse<U, F>(_fn: (error: E) => Result<U, F>): Result<T | U, F> {
return makeOk<T | U, F>(value);
},
unwrapOr<U>…채점 근거discriminated union + 메서드 + AsyncResult wrapper + andThen 에러 union 누적 + infer/conditional 타입 추출 모두 의미적으로 충족. 부적합 케이스(버그/불변식)도 적절. fp-ts/neverthrow 등 권위 비교가 빠진 점이 s_structure/g에서 사소한 감점.
시그니처“andThen을 거치며 ParseErr | PositiveErr 로 에러 타입이 자동 누적됩니다.”
MiniMax M3Minimax729095609283
```typescript
// result.ts — Rust-style Result<T, E> for TypeScript 5.9
// ============================================================================
// ── (1) Discriminated union ───────────────────────────────────────────────
export type Ok<T> = { readonly ok: true; readonly value: T };
export type Err<E> = { readonly ok: false; readonly error: E };
export type Result<T, E> = Ok<T> | Err<E>;
// ── (4) Type-level inference helpers (infer + conditional) ───────────────
export type InferOk<R> = R extends Result<infer T, unknown> ? T : never;
export type InferErr<R> = R extends Result<unknown, infer E> ? E : never;
export type IsOk<R> = R extends Ok<unknown> ? true : false;
export type IsErr<R> = R extends Err<unknown> ? true : false;
/** Result<Result<T, E>, E> → Result<T, E> (recursive flatten) */
export type Flatten<R> =
R extends Result<infer T, infer E>
? T extends Result<unknown, unknown> ? Flatten<T>
: Result<T, E>
: never;
// 가벼운 생성자 (간결 표기용)
export const ok = <T>(value: T): Result<T, never> => ({ ok: true, value });
export const err = <E>(error: E): Result<never, E> => ({ ok: false, error });
// ============================================================================
// (2) Result class — map / mapErr / andThen / orElse / unwrapOr
// ============================================================================
export class Result<T, E> {
readonly #state: Ok<T> | Err<E>;
private constructor(state: Ok<T> | Err<E>) { this.#state = state; }
// ── Constructors ───────────────────────────────────────────────────────
static ok<T>(value: T): Result<T, never> {
return new Result<T, never>({ ok: true, value });
}
static err<E>(error: E): Result<never, E> {
return new Result<never, E>({ ok: false, error });
}
/** 던지는 함수를 Result로 감싸기 */
static from<T, E = unknown>(fn: () => T, catchFn?: (e: unknown) => E): Result<T, E> {
try { return Result.ok(fn()); }
catch (e) { return Result.err(catchFn ? catchFn(e) : (e as E)); }
}
/** 모두 Ok면 Ok<array>, 첫 Err에서 단락 */
static all<T, E>(rs: readonly Result<T, E>[]): Result<readonly T[], E> {
const out: T[] = [];
for (const r of rs) {
if (r.isOk()) out.push(r.value as T);
else return r as unknown as Result<readonly T[], E>;
}
return Result.ok(out);
}
// ── 타입 좁히기 predicate ─────────────────────────────────────────────
isOk(): this is Ok<T> { return this.#state.ok; }
isErr(): this is Err<E> { return !this.#state.ok; }
// ── 안전한 접근자 ─────────────────────────────────────────────────────
get value(): T | undefined { return this.#state.ok ? this.#state.value : undefined; }
get error(): E | undefined { return this.#state.ok ? undefined : this.#state.error; }
// ── Combinators ───────────────────────────────────────────────────────
map<U>(fn: (value: T) => U): Result<U, E> {
const s = this.#state;
return s.ok ? Result.ok(fn(s.value)) : new Result<U, E>(s);
}
mapErr<F>(fn: (error: E) => F): Result<T, F> {
const s = this.#state;
return s.ok ? new Result<T, F>(s) : Result.err(fn(s.error));
}
/** chain on Ok. 반환 타입의 E' 가 E | F 로 widening → 체이닝 시 union 누적 */
andThen<U, F>(fn: (value: T) => Result<U, F>): Result<U, E | F> {
const s = this.#state;
return s.ok ? fn(s.value) : new Result<U, E | F>(s);
}
/** recover on Err. T 는 유지하고 error 만 교체 가능 */
orElse<F>(fn: (error: E) => Result<T, F>): Result<T, F> {
const s = this.#state;
return s.ok ? new Result<T, F>(s) : fn(s.error);
}
unwrapOr(fallback: T): T {
return this.#state.ok ? this.#state.value : fallback;
}
/** 소진성 패턴 매치 — 모든 분기를 강제 */
match<R>(patterns: { ok: (value: T) => R; err: (error: E) => R }): R {
const s = this.#state;
return s.ok ? patterns.ok(s.value) : patterns.err(s.error);
}
expect(msg: string): T {
if (this.#state.ok) return this.#state.value;
throw new Error(`${msg}: ${JSON.stringify(this.#state.error)}`);
}
}
// ============================================================================
// (3) AsyncResult<T, E> — Promise<Result<T, E>> 와 같은 패턴으로 chain
// ============================================================================
export class AsyncResult<T, E> {
readonly #promise: Promise<Result<T, E>>;
private constructor(promise: Promise<Result<T, E>>) { this.#promise = promise; }
// ── Constructors ───────────────────────────────────────────────────────
static ok<T>(value: T | PromiseLike<T>): AsyncResult<T, never> {
return new AsyncResult<T, never>(
Promise.resolve(value).then(v => Result.ok(v)),
);
}
static err<E>(error: E): AsyncResult<never, E> {
return new AsyncResult<never, E>(Promise.resolve(Result.err(error)));
}
/** 기존 Promise<Result<T,E>> 를 chain 가능한 형태로 감쌈 */
static from<T, E>(promise: Promise<Result<T, E>>): AsyncResult<T, E> {
return new AsyncResult(promise);
}
/** 던지는 async 함수를 AsyncResult 로 */
static async try<T, E = unknown>(
fn: () => Promise<T>,
catchFn?: (e: unknown) => E,
): Promise<AsyncResult<T, E>> {
try { return AsyncResult.ok(await fn()); }
catch (e) { return AsyncResult.err(catchFn ? catchFn(e) : (e as E)); }
}
// ── Combinators — 시그니처는 Result 와 1:1 (콜백이 비동기일 수 있다는 점만 추가) ──
map<U>(fn: (value: T) => U | PromiseLike<U>): AsyncResult<U, E> {
const p = this.#promise;
return new AsyncResult<U, E>(
p.then(async r => r.isOk()
? Result.ok(await fn(r.value as T))
: (r as unknown as Result<U, E>)),
);
}
mapErr<F>(fn: (error: E) => F | PromiseLike<F>): AsyncResult<T, F> {
const p = this.#promise;
return new AsyncResult<T, F>(
p.then(async r => r.isErr()
? Result.err(await fn(r.error as E))
: (r as unknown as Result<T, F>)),
);
}
andThen<U, F>(
fn: (value: T) => Result<U, F> | Promise<Result<U, F>> | AsyncResult<U, F>,
): AsyncResult<U, E | F> {
const p = this.#promise;
return new AsyncResult<U, E | F>(
p.then(async r => {
if (!r.isOk()) return r as unknown as Result<U, E | F>;
const next = await fn(r.value as T);
return next instanceof AsyncResult
? ((await next.toPromise()) as Result<U, F>)
: (next as Result<U, F>);
}),
);
}
orElse<F>(
fn: (error: E) => Result<T, F> | Promise<Result<T, F>> | AsyncResult<T, F>,
): AsyncResult<T, F> {
const p = this.#promise;
return new AsyncResult<T, F>(
p.then(async r => {
if (!r.isErr()) return r as unknown as Result<T, F>;
const next = await fn(r.error as E);
return next instanceof AsyncResult
? ((await next.toPromise()) as Result<T, F>)
: (next as Result<T, F>);
}),
);
}
unwrapOr(fallback: T | PromiseLike<T>): Promise<T> {
return this.#promise.then(async r =>
r.isOk() ? (r.value as T) : await fallback,
);
}
async match<R>(patterns: {
ok: (value: T) => R | PromiseLike<R>;
err: (error: E) => R | PromiseLike<R>;
}): Promise<R> {
const r = await this.#promise;
return r.match(patterns);
}
toPromise(): Promise<Result<T, E>> { return this.#promise; }
}
```
### (4) 체이닝 시 error union 자동 누적 — 원리
`andThen` 의 반환 타입이 `Result<U, E | F>` 로 widening 됩니다. 그래서 두 번째 `andThen` 호출에서는 그 widened 타입이 새로운 `E` 로 들어가고, 거기서 또 `| F₂` 가 붙어 **자동 누적**됩니다.
```typescript
// E₁ = ConfigError, E₂ = HttpError, E₃ = ParseError
declare const a: Result<Host, ConfigError>;
declare const b: AsyncResult<Port, HttpError>;
declare const c: Result<Body, ParseError>;
const r: AsyncResult<Html, ConfigError | HttpError | ParseError> =
AsyncResult.from(Promise.resolve(a))
.andThen(host => b) // | HttpError
.andThen(port => c.map(body => ({host,port,body}))); // | ParseError
```
> `Promise<Result<T,E>>` 는 discriminated union 이 아니어서 TS 가 `r.isOk()` 으로 narrowing 하지 못합니다. 그래서 `AsyncResult` 클래스로 한 번 감싸서 같은 모양의 메서드들을 노출합니다 — 본질은 *“`Promise` 의 then-chain 을 `Result` 의 combinator 로 재작성”* 한 것입니다.
---
### (5) 사용 예 3가지
#### 예 1 — 설정 파싱 파이프라인 (동기, 에러 union 누적)
```typescript
type ConfigError =
| { kind: "missing"; key: string }
| { kind: "invalidInt"; key: string; raw: string }
| { kind: "outOfRange"; key: string; min: number; max: number; got: number };
const requireField =
(raw: Record<string, string>, key: string): Result<string, ConfigError> =>
raw[key] ? Result.ok(raw[key]) : Result.err({ kind: "missing", key });
const parsePort = (s: string): Result<number, ConfigError> => {
const n = Number(s);
if (!Number.isInteger(n)) return Result.err({ kind: "invalidInt", key: "port", raw: s });
if (n < 1 || n > 65535) return Result.err({ kind: "outOfRange", key: "port", min: 1, max: 65535, got: n });
return Result.ok(n);
};
function loadConfig(raw: Record<string, string>): Result<AppConfig, ConfigError> {
// Result<string, ConfigError>
// .andThen(...) -> Result<string, ConfigError> ← E 그대로
// .andThen(...) -> Result<number, ConfigError> ← E 그대로
// .map(...) -> Result<{host,port}, ConfigError> ← U 만 변경
return requireField(raw, "host")
.andThen(host =>
requireField(raw, "port")
.andThen(parsePort)
.map(port => ({ host, port })));
}
loadConfig(process.env).match({
ok: cfg => startServer(cfg),
err: e => log.error(`config.${e.kind}`, e),
});
```
#### 예 2 — 비동기 파이프라인 (다른 모듈의 에러 union 자동 누적)
```typescript
type AuthErr = { kind: "unauthorized" };
type NetErr = { kind: "network"; status: number };
type ParseErr = { kind: "parse"; line: number; msg: string };
const fetchUser = (id: number): AsyncResult<User, AuthErr | NetErr> => /* … */;
const fetchPost = (id: number, t: string): AsyncResult<Post, AuthErr | NetErr> => /* … */;
const renderHtml = (md: string): Result<string, ParseErr> => /* … */;
async function loadPostHtml(postId: number)
: AsyncResult<{ html: string }, AuthErr | NetErr | ParseErr> // ← 누적된 E
{
return fetchUser(1) // AsyncResult<U₁, AuthErr|NetErr>
.andThen(user => fetchPost(postId, user.token)) // AsyncResult<U₂, AuthErr|NetErr|AuthErr|NetErr> (=동일)
.andThen(post => AsyncResult.from( // AsyncResult<U₃, …|ParseErr>
Promise.resolve(renderHtml(post.body)),
).map(html => ({ html })));
}
// 터미널 처리
const r = await loadPostHtml(42);
r.match({
ok: ({ html }) => res.send(html),
err: e => e.kind === "unauthorized" ? res.sendStatus(401)
: res.status(502).send(e),
});
```
#### 예 3 — 에러 정규화 + `Result.from` 로 throwing 코드 흡수
```typescript
type AppError =
| { kind: "io"; code: string; path: string }
| { kind: "json"; message: string; path: string }
| { kind: "schema"; field: string; reason: string };
const readJsonFile = (path: string): Result<unknown, AppError> =>
Result.from(
() => JSON.parse(require("node:fs").readFileSync(path, "utf-8")),
(e): AppError => {
const err = e as NodeJS.ErrnoException;
if (err?.code) return { kind: "io", code: err.code, path };
if (e instanceof SyntaxError) return { kind: "json", message: e.message, path };
return { kind: "io", code: "UNKNOWN", path };
},
);
const asUser = (raw: unknown): Result<User, AppError> => {
if (typeof raw !== "object" || raw === null)
return Result.err({ kind: "schema", field: "<root>", reason: "not an object" });
const r = raw as Record<string, unknown>;
if (typeof r.id !== "number") return Result.err({ kind: "schema", field: "id", reason: "not number" });
if (typeof r.name !== "string") return Result.err({ kind: "schema", field: "name", reason: "not string" });
return Result.ok({ id: r.id as number, name: r.name as string });
};
function loadUser(path: string): Result<User, AppError> {
return readJsonFile(path).andThen(asUser); // E = AppError
}
```
---
### Result 패턴이 **부적합한** 케이스
**“절대 일어나선 안 되는” invariant / 프로그래머 오류.**
```typescript
// ❌ Result 로 “에러를 값으로” 다루려는 시도 — 하지만 이건 *버그* 다
function shift<T>(arr: T[], i: number): Result<T, "out_of_range"> {
if (i < 0 || i >= arr.length) return err("out_of_range"); // 호출자가 잘못 쓴 거임
return ok(arr[i]);
}
// ✅ 그냥 throw — stack trace 와 함께 죽고, 프로세스는 멈춰야 함
function shift<T>(arr: T[], i: number): T {
if (i < 0 || i >= arr.length) throw new RangeError(`shift: index ${i}`);
return arr[i];
}
```
> **규칙**: `Result` 는 *호출자가 정상적으로 처리해야 할 “예상된 실패”* — 네트워크, 파싱, 검증, 권한 — 에 쓰세요. *“이 분기에 도달하면 코드가 잘못된 것”* 같은 invariant, OOM, 프로그래머 버그는 `throw` 하고 죽이는 게 맞습니다. 예외가 stack trace 를 남기고, 도달 불가능한 코드라는 신호를 타입으로 표현할 수도 있습니다 (`assertNever(e)` 같은 소진성 매처).
---
### 참고: 타입 인퍼런스가 동작하는 지점
| 호출 | 추론된 타입 | 누적 E |
| ------------------------------------- | ---------------------------------------- | ------------------ |
| `requireField(...).andThen(parsePort)` | `Result<number, ConfigError>` | `ConfigError` |
| `.andThen(parsePort).map(p => …)` | `Result<{host,port}, ConfigError>` | `ConfigError` |
| `fetchUser(1).andThen(fetchPost(...))` | `AsyncResult<Post, AuthErr \| NetErr>` | `AuthErr\|NetErr` |
| `.andThen(p => AsyncResult.from(…))` | `AsyncResult<{html}, … \| ParseErr>` | `…\|ParseErr` |
`andThen` 의 두 번째 타입 파라미터 `F` 가 함수 반환 `Result<U, F>` 에서 추론되어 `E | F` 로 합쳐지고, 그 결과가 다음 호출의 `E` 슬롯으로 들어가기 때문에 — **TS 가 직접 해주므로 별도 generic 표기 없이 union 이 누적**됩니다.채점 근거TypeScript Result: ★andThen error 누적 union `Result<U,E|F>` 정확 타이핑+추론표로 증명(q5 g 변별점 적중)·infer/conditional(InferOk/Flatten)·AsyncResult·★부적합 케이스(invariant/프로그래머 오류는 throw) calibration 교과서급·3 사용예. 개념 깊이는 보드 최상. 단 ★컴파일 에러: `type Result<T,E>` + `class Result<T,E>` 중복식별자(TS2300) + 독립 ok/err가 클래스 인스턴스 아닌 plain object라 클래스 메서드와 비호환 + isOk()의 this is Ok<T> 가드가 구조적으로 불건전. 1회 rename이면 풀리나 붙여넣기 실행 불가 → g 실점.
Gemini 3.1 ProGoogle808080608078
TypeScript에서 Rust의 `Result<T, E>` 패턴을 구현하면 예외(Exception)를 값으로 안전하게 다룰 수 있으며, 타입 시스템을 통해 발생 가능한 에러를 명시적으로 추적할 수 있습니다.
요구사항에 맞춘 `Result` 및 `AsyncResult`의 전체 구현과 활용 예시입니다.
---
### 1 & 2. `Result<T, E>` 타입 및 체이닝 유틸리티 구현
Discriminated Union을 활용하여 `isOk` 프로퍼티로 타입을 좁힐(Narrowing) 수 있도록 구현합니다.
```typescript
// --- 1. Base Interface ---
export interface IResult<T, E> {
map<U>(fn: (val: T) => U): Result<U, E>;
mapErr<F>(fn: (err: E) => F): Result<T, F>;
andThen<U, F>(fn: (val: T) => Result<U, F>): Result<U, E | F>;
orElse<U, F>(fn: (err: E) => Result<U, F>): Result<T | U, F>;
unwrapOr<U>(defaultVal: U): T | U;
}
// --- 2. Ok & Err Classes ---
export class Ok<T, E = never> implements IResult<T, E> {
readonly isOk: true = true;
readonly isErr: false = false;
constructor(public readonly value: T) {}
map<U>(fn: (val: T) => U): Result<U, E> {
return new Ok(fn(this.value));
}
mapErr<F>(fn: (err: E) => F): Result<T, F> {
return new Ok<T, F>(this.value); // 에러가 없으므로 값 유지
}
andThen<U, F>(fn: (val: T) => Result<U, F>): Result<U, E | F> {
return fn(this.value); // 성공했으므로 다음 함수 실행 (에러 타입 E | F 누적)
}
orElse<U, F>(fn: (err: E) => Result<U, F>): Result<T | U, F> {
return new Ok<T | U, F>(this.value);
}
unwrapOr<U>(defaultVal: U): T | U {
return this.value;
}
}
export class Err<T, E> implements IResult<T, E> {
readonly isOk: false = false;
readonly isErr: true = true;
constructor(public readonly error: E) {}
map<U>(fn: (val: T) …채점 근거Discriminated union + 클래스 구현 + andThen의 E|F 누적 추론을 정확히 보여줬고 AsyncResult, orElse 에러 타입 전환까지 다룸. 다만 fp-ts/neverthrow/Rust Result 권위 비교가 누락되어 s_structure·g_grounding 만점에는 못 미치고, infer/conditional type 활용은 사실상 없음.
시그니처“andThen 한 줄로 E | F 에러 타입이 자동 누적된다.”
Claude Sonnet 4.6Anthropic8080806010084
# Rust-style `Result<T, E>` + `AsyncResult<T, E>` for TypeScript 5.9
## Core Library (`result.ts`)
```typescript
// ══════════════════════════════════════════════════════════════════════════════
// result.ts — Rust-style Result<T, E> + AsyncResult<T, E> (TypeScript 5.9)
// ══════════════════════════════════════════════════════════════════════════════
// ── §0. ResultUnwrapError ─────────────────────────────────────────────────────
export class ResultUnwrapError<E> extends Error {
constructor(public readonly cause: E) {
super(
`unwrap() called on Err: ${
cause instanceof Error ? cause.message : JSON.stringify(cause)
}`
);
this.name = "ResultUnwrapError";
}
}
// ── §1. Utility / Reflection Types ───────────────────────────────────────────
/** Extract the Ok-value type from a Result */
export type OkOf<R> =
R extends Result<infer T, infer _E> ? T :
R extends AsyncResult<infer T, infer _E> ? T :
never;
/** Extract the Err type from a Result */
export type ErrOf<R> =
R extends Result<infer _T, infer E> ? E :
R extends AsyncResult<infer _T, infer E> ? E :
never;
/**
* Compute the cumulative error type after one andThen step.
* ChainErr<'NotFound', Result<Post, 'Forbidden'>> → 'NotFound' | 'Forbidden'
*/
export type ChainErr<E, StepResult extends Result<unknown, unknown>> =
E | ErrOf<StepResult>;
/**
* Distribute OkOf/ErrOf across a heterogeneous Result tuple.
* Used by combineAll to preserve per-elemen…채점 근거discriminated union(_tag)+andThen E|F 자동누적+orElse 에러변환+infer 리플렉션(OkOf/ErrOf/UnzipResults/ChainErr)+never+any회피(FFI경계만 unknown)+const 타입파라미터+AsyncResult+match망라+anti-pattern 3종(프로그래밍버그/피라미드중첩/never-fail)+설계요약표+Usage예제. 단 명시적 fp-ts/neverthrow 라이브러리 비교 누락(권위 차원)
Gemini 3.5 FlashGoogle8080808010086
TypeScript 5.9의 최신 타입 시스템 기능(Conditional Types, `infer`, template literal types, `satisfies` 등)을 활용하여, **Rust 스타일의 `Result<T, E>`** 및 비동기 스트림 처리를 위한 **`AsyncResult<T, E>`**를 완벽한 타입 안전성과 함께 구현했습니다.
---
### 1. 핵심 타입 및 클래스 정의
이 구현은 `any`를 전혀 사용하지 않으며, 내부 캐스팅을 최소화하고 사용자 관점에서는 **100% 완벽한 타입 추론 및 에러 타입 누적(Union Accumulation)**이 작동하도록 설계되었습니다.
```typescript
// ==========================================
// 1. discriminated union 및 헬퍼 타입 정의
// ==========================================
export type ResultUnion<T, E> =
| { readonly ok: true; readonly value: T }
| { readonly ok: false; readonly error: E };
// 전달된 객체(Result, AsyncResult, Promise)로부터 Ok/Err 타입을 추출하는 유틸리티 타입
export type InferOk<R> =
R extends Result<infer T, any> ? T :
R extends AsyncResult<infer T, any> ? T :
R extends Promise<Result<infer T, any>> ? T :
never;
export type InferErr<R> =
R extends Result<any, infer E> ? E :
R extends AsyncResult<any, infer E> ? E :
R extends Promise<Result<any, infer E>> ? E :
never;
// AsyncResult 체이닝에서 허용하는 반환 형식들
export type AsyncResultLike<T, E> =
| Result<T, E>
| Promise<Result<T, E>>
| AsyncResult<T, E>;
// ==========================================
// 2. 동기 Result<T, E> 클래스 구현
// ==========================================
export class Result<T, E> {
// discriminated union 상태를 readonly로 노출 (패턴 매칭 및 내부 최적화용)
readonly state: ResultUnion<T, E>;
private constructor(state: ResultUnion<T, E>) {
this.state = state;
}
static ok<T, E = never…채점 근거모든 요건: discriminated union+5메서드+AsyncResult+infer/never/conditional로 error union 자동누적+any 회피 명시+사용예3+부적합1. 최다 완성도
Nemotron 3 Ultra 550BNVIDIA728072788578
```ts
// ------------------------------------------------------------
// 1️⃣ Result<T,E> – discriminated union (ok / err)
// ------------------------------------------------------------
type Ok<T> = { readonly ok: true; readonly value: T };
type Err<E> = { readonly ok: false; readonly error: E };
export type Result<T, E> = (Ok<T> | Err<E>) & {
// ----- synchronous combinators ---------------------------------
/** value가 있을 때만 변환 */
map<U>(fn: (v: T) => U): Result<U, E>;
/** 에러만 변환 */
mapErr<F>(fn: (e: E) => F): Result<T, F>;
/** value가 있을 때 새로운 Result 를 반환 → 에러 타입이 합집합으로 누적 */
andThen<U, E2>(fn: (v: T) => Result<U, E2>): Result<U, E | E2>;
/** 에러일 때 복구용 Result 를 반환 → 성공 타입이 합집합, 에러 타입은 fn 의 에러만 남음 */
orElse<U, E2>(fn: (e: E) => Result<U, E2>): Result<T | U, E2>;
/** 기본값으로 언랩 */
unwrapOr<U>(fallback: U): T | U;
};
// ------------------------------------------------------------
// 2️⃣ 구현 – 순수 함수로 제공 (union 에 메서드를 “혼합”)
// ------------------------------------------------------------
const _ok = <T>(value: T): Ok<T> => ({ ok: true, value });
const _err = <E>(error: E): Err<E> => ({ ok: false, error });
export const Result = {
/** 성공 Result 생성 */
ok<T>(value: T): Result<T, never> {
return Object.assign(_ok(value), proto);
},
/** 실패 Result 생성 */
err<E>(error: E): Result<never, E> {
return Object.assign(_err(error), proto);
},
/** 내부 프로토타입 – 모든 메서드가 이 객체를 통해 위임됨 */
// (타입만 맞추기 위해 any 를 최소한으로 사용)
// eslint-disable-next-li…채점 근거Result<T,E> Rust-style 체이닝. Ok/Err discriminated union + map/mapErr/andThen/orElse/unwrapOr + ★andThen 에러타입 유니온 누적(E|E2) 시그니처 정확(핵심 요구) + orElse E2 좁힘 + 부적합 케이스 분석. 단 ★AsyncResult를 자유함수(AsyncResult.map(ar,fn))로 구현해놓고 예시3은 .andThen().map() 메서드 체이닝(raw Promise엔 해당 메서드 없음=예시 미실행 버그) + proto self-reference(Result.proto={}as any vs const proto 중복) awkward. 타입설계 핵심은 적중, 비동기 체이닝 예시만 broken. deepseek-flash 인근.
DeepSeek V4 FlashDeepSeek10080807010091
다음은 TypeScript 5.9에서 Rust 스타일 `Result<T, E>` 타입과 체이닝 유틸리티를 구현한 코드입니다.
`any` / 과도한 `unknown`을 배제하고, `never`·`infer`·조건부 타입을 적극 활용하여 타입 안전성을 최대한 보장했습니다.
```typescript
// ============================================================
// 1. 기본 Result discriminated union
// ============================================================
export interface Ok<T, E = never> {
readonly _tag: 'ok';
readonly value: T;
}
export interface Err<T = never, E = unknown> {
readonly _tag: 'err';
readonly error: E;
}
export type Result<T, E> = Ok<T, E> | Err<T, E>;
// ============================================================
// 2. Ok / Err 생성 헬퍼 (정적 타입 추론 최적화)
// ============================================================
export const ok = <T, E = never>(value: T): Ok<T, E> => ({
_tag: 'ok' as const,
value,
});
export const err = <T = never, E>(error: E): Err<T, E> => ({
_tag: 'err' as const,
error,
});
// ============================================================
// 3. Result 클래스 – 체이닝 메서드 제공
// ============================================================
export class ResultImpl<T, E> {
private constructor(private readonly result: Result<T, E>) {}
// 정적 팩토리
static ok<T, E = never>(value: T): ResultImpl<T, E> {
return new ResultImpl(ok<T, E>(value));
}
static err<T = never, E>(error: E): ResultImpl<T, E> {
return new ResultImpl(err<T, E>(error));
}
// 내부 값 접근 (readonly)
unwrap(): T {
if (this.result._tag === 'ok') return this.result.value…채점 근거discriminated union+andThen 누적+AsyncResult+fp-ts/neverthrow 비교+infer 활용. 최상
Qwen 3.7 PlusAlibaba808280648279
TypeScript 5.x의 엄격한 타입 시스템(Strict Mode)을 활용하여 Rust 스타일의 `Result` 패턴을 구현했습니다. `any`를 배제하고, `never`, 제네릭 기본값, 그리고 Discriminated Union을 최대한 활용하여 타입 안전성과 체이닝 시 에러 타입의 자동 누적(Accumulation)을 보장합니다.
---
### 1. 핵심 타입 및 클래스 정의
```typescript
// (1) Discriminated Union 정의
export interface Ok<T> {
readonly ok: true;
readonly value: T;
}
export interface Err<E> {
readonly ok: false;
readonly error: E;
}
export type ResultUnion<T, E> = Ok<T> | Err<E>;
// (2) Result 클래스 (메서드 체이닝 제공)
export class Result<T, E> implements ResultUnion<T, E> {
public readonly ok: boolean;
public readonly value: T;
public readonly error: E;
private constructor(ok: boolean, value: T | undefined, error: E | undefined) {
this.ok = ok;
// 생성자 로직에 의해 타입 단언은 안전함 (ok가 true면 value는 T, false면 error는 E)
this.value = value as T;
this.error = error as E;
}
public static ok<T, E = never>(value: T): Result<T, E> {
return new Result<T, E>(true, value, undefined);
}
public static err<T = never, E = unknown>(error: E): Result<T, E> {
return new Result<T, E>(false, undefined, error);
}
// 값이 존재할 때만 변환 (에러 타입 유지)
public map<U>(fn: (value: T) => U): Result<U, E> {
return this.ok
? Result.ok<U, E>(fn(this.value))
: Result.err<U, E>(this.error);
}
// 에러가 존재할 때만 변환 (성공 타입 유지)
public mapErr<F>(fn: (error: E) => F): Result<T, F> {
return this.ok
? Result.ok<T, F>(this.value)
: Result.err<T, F>(fn(this.error));
}
// (4) 타입 추론: an…채점 근거TS Result — Discriminated union(Ok<T>|Err<E>) + 클래스 메서드(map/mapErr/andThen/orElse/unwrapOr) + ★ andThen 체이닝 에러 타입 E|F 자동 누적 추론 정확(gold 핵심 함정 통과) + AsyncResult<T,E> + tryCatch 래핑 + 사용예 3(검증체이닝 누적·async 폴백·map/mapErr) + ★ 부적합 케이스(invariant 위배·복구불가·프로그래머 버그는 throw) 한계 명시. BUT Result '클래스'가 ok:boolean으로 선언돼 discriminated narrowing이 union만큼 안 됨(value/error 양쪽 항상 접근 가능) + toPromise() '가상 메서드 가정' hand-wave + fp-ts/neverthrow 라이브러리 명시 비교 약함(Rust만 언급).
시그니처“qwen3.7-plus, Result andThen 에러 E|F 누적 추론+AsyncResult+한계 케이스 정확, 단 클래스 ok:boolean narrowing 흠·fp-ts 비교 약함”
DeepSeek V4 ProDeepSeek808080708079
다음은 TypeScript 5.9에서 Rust 스타일의 `Result<T, E>`와 `AsyncResult<T, E>`를 구현한 코드입니다. 조건부 타입과 제네릭을 활용하여 `andThen` 체이닝 시 에러 타입을 자동으로 유니언 누적하며, `any`나 과도한 `unknown`을 사용하지 않습니다.
```typescript
// ---------------------------------------------------------
// 1. Result<T, E> discriminated union (ok / err 태그 포함)
// ---------------------------------------------------------
abstract class Result<T, E> {
abstract readonly tag: 'ok' | 'err';
// 핵심 메서드들 (구현은 하위 클래스에서)
abstract map<U>(fn: (value: T) => U): Result<U, E>;
abstract mapErr<F>(fn: (error: E) => F): Result<T, F>;
abstract andThen<U, F>(fn: (value: T) => Result<U, F>): Result<U, E | F>;
abstract orElse<F>(fn: (error: E) => Result<T, F>): Result<T, F>;
abstract unwrapOr(defaultValue: T): T;
// 정적 팩토리
static ok<T, E = never>(value: T): Result<T, E> {
return new Ok<T>(value) as Result<T, E>;
}
static err<T = never, E>(error: E): Result<T, E> {
return new Err<E>(error) as Result<T, E>;
}
}
// Ok: T 값을 가지고, E는 never
class Ok<T> extends Result<T, never> {
readonly tag = 'ok';
constructor(public readonly value: T) { super(); }
map<U>(fn: (value: T) => U): Result<U, never> {
return new Ok(fn(this.value));
}
mapErr<F>(_fn: (error: never) => F): Result<T, F> {
// Ok에는 에러가 없으므로 그대로 반환
return this as unknown as Result<T, F>;
}
andThen<U, F>(fn: (value: T) => Result<U, F>): Result<U, never | F> {
// never | F → F
return fn(this.value) as Result<U, F>;
}
orElse<F>(_fn: (err…채점 근거discriminated union+andThen+error union+AsyncResult+라이브러리 비교
Mimo V2.5 ProXiaomi808080608078
# Rust 스타일 `Result<T, E>` — TypeScript 구현
## 설계 요약
| 핵심 기법 | 목적 |
|---|---|
| `Ok<T> \| Err<E>` discriminated union | 런타임 태그(`_tag`)로 분기 |
| `F = never` default type param | 에러 없을 때 `E \| never = E` 유지 |
| `NoInfer<T>` | `unwrapOr`의 fallback이 `T`를 widen하지 않도록 차단 |
| `never` · `infer` · conditional type | `InferOk` · `InferErr` 유틸리티 추출 |
---
## 전체 코드
```typescript
// ============================================================================
// result.ts — Rust 스타일 Result<T, E> discriminated union + 체이닝
// TypeScript ≥ 5.4 (NoInfer<T> 사용)
// ============================================================================
// ─── 1. 타입 정의 ──────────────────────────────────────────────────────────
/** 성공값을 담는 태그 */
interface Ok<T> {
readonly _tag: "ok";
readonly value: T;
}
/** 에러값을 담는 태그 */
interface Err<E> {
readonly _tag: "err";
readonly error: E;
}
/**
* Rust `Result<T, E>`에 대응하는 discriminated union.
* - `isOk(result)` / `isErr(result)` 로 분기
* - 구조적 타입이므로 plain object 리터럴과 호환
*/
type Result<T, E> = Ok<T> | Err<E>;
/** Promise로 감싼 Result — async 파이프라인 전용 */
type AsyncResult<T, E> = Promise<Result<T, E>>;
// ─── 조건부 타입 유틸 ──────────────────────────────────────────────────────
/** Result에서 Ok 값 타입 추출 */
type InferOk<R> = R extends Result<infer T, unknown> ? T : never;
/** Result에서 Err 값 타입 추출 */
type InferErr<R> = R extends Result<unknown, infer E> ? E : never;
// ─── 2. 생성자 & 타입 가드 ─────────────────────────────────────────────────
function ok<T>(value…채점 근거39.2K tok 완주. discriminated union(_tag)+andThen E|F 자동누적+infer/never/conditional+AsyncResult+anti-pattern(자원정리/I/O/스택트레이스 vs Result 분기)+3예제(에러 union 누적). 12.6K자 적정. 단 명시적 fp-ts/neverthrow 라이브러리 비교 누락(권위)→k60
GPT-5.4 MiniOpenAI808080808080
아래는 **TypeScript 5.9** 기준으로 바로 쓸 수 있는, Rust 스타일의 `Result<T, E>` / `AsyncResult<T, E>` 유틸리티 예시입니다.
- `ok` / `err`로 만드는 **discriminated union**
- `map`, `mapErr`, `andThen`, `orElse`, `unwrapOr`
- `Promise<Result<T, E>>`도 체이닝 가능하도록 `AsyncResult`
- `andThen` 체이닝 시 **error 타입 union 자동 누적**
- `infer` / conditional type 활용
---
## 구현
```ts
// =========================
// Result core
// =========================
type Awaitable<T> = T | Promise<T>;
export type ResultValue<R> = R extends Result<infer T, infer _E> ? T : never;
export type ResultError<R> = R extends Result<infer _T, infer E> ? E : never;
export interface Ok<T, E = never> {
readonly ok: true;
readonly err: false;
readonly value: T;
map<U>(fn: (value: T) => U): Result<U, E>;
mapErr<F>(fn: (error: E) => F): Result<T, F>;
andThen<U, E2>(fn: (value: T) => Result<U, E2>): Result<U, E | E2>;
orElse<U, F>(fn: (error: E) => Result<U, F>): Result<T | U, F>;
unwrapOr<D>(defaultValue: D): T extends never ? D : T;
}
export interface Err<T = never, E = never> {
readonly ok: false;
readonly err: true;
readonly error: E;
map<U>(fn: (value: T) => U): Result<U, E>;
mapErr<F>(fn: (error: E) => F): Result<T, F>;
andThen<U, E2>(fn: (value: T) => Result<U, E2>): Result<U, E | E2>;
orElse<U, F>(fn: (error: E) => Result<U, F>): Result<T | U, F>;
unwrapOr<D>(defaultValue: D): T extends never ? D : T;
}
export type Result<T, E> = Ok<T, E> | Err<T, E>;
function createOk<T, E = never>(value: T): Ok<T, E…채점 근거infer/never/conditional+AsyncResult+error union 누적+부적합 케이스. 견고
Kimi K2.6Moonshot808080808080
아래는 **TypeScript 5.9** 기준으로, Rust 스타일의 `Result<T, E>`를 discriminated union 으로 정의하고 체이닝 메서드를 제공하는 구현입니다.
`any` 없이 `never` / `infer` / conditional type 을 적극 활용했으며, `AsyncResult`를 통해 `Promise<Result<T, E>>` 도 동일한 패턴으로 체이닝할 수 있습니다.
---
### 1. Core Types — `Result<T, E>` Discriminated Union
```typescript
// ============================================================
// Discriminated Union 타입 정의
// ============================================================
type Result<T, E> = Ok<T, E> | Err<T, E>;
// -----------------------------------------------------------
// Ok<T, E = never>
// 성공 브랜치. E는 타입 추론/호환용으로만 존재하며 실제 인스턴스에는 노출되지 않음
// -----------------------------------------------------------
class Ok<T, E = never> {
readonly kind = 'ok' as const;
constructor(readonly value: T) {}
map<U>(f: (value: T) => U): Result<U, E> {
// Ok는 에러를 가질 수 없으므로 E가 그대로 유지됨
return new Ok(f(this.value));
}
mapErr<F>(_f: (error: E) => F): Result<T, F> {
// 에러가 없으므로 그냥 자신을 반환. 구조적 타이핑 덕분에 Result<T, F> 로 안전하게 승격
return new Ok(this.value);
}
andThen<U, F>(f: (value: T) => Result<U, F>): Result<U, E | F> {
// 기존 에러 E 와 새로운 에러 F 가 union 으로 누적됨
return f(this.value);
}
orElse<F>(_f: (error: E) => Result<T, F>): Result<T, F> {
return new Ok(this.value);
}
unwrapOr(_defaultValue: T): T {
return this.value;
}
}
// -----------------------------------------------------------
// Err<T = never, E>
// 실패 브랜치. T는 타입 추론/호환용으로만 존재
// --------------------…채점 근거전 요건+infer/never+AsyncResult+error union 누적. 견고
GLM 5.1Z.ai808080808080
TypeScript 5.9의 타입 시스템을 활용하여 Rust 스타일의 `Result<T, E>` 타입과 체이닝 유틸리티를 작성했습니다.
요구사항에 맞춰 `any`와 과도한 `unknown` 사용을 지양하고, `never`를 활용한 타입 안전한 구조와 `infer`를 활용한 유틸리티 타입을 포함했습니다. 특히 `andThen` 체이닝 시 에러 타입이 자동으로 누적되는 핵심 로직에 집중했습니다.
---
### 1. 타입 및 클래스 정의 (`Result<T, E>` & `AsyncResult<T, E>`)
```typescript
// ==========================================
// 1. 기본 타입 정의 (Discriminated Union)
// ==========================================
export type Result<T, E> = Ok<T, E> | Err<E, T>;
// infer 및 conditional type을 활용한 유틸리티 타입
export type ResultOk<R> = R extends Result<infer T, infer _E> ? T : never;
export type ResultErr<R> = R extends Result<infer _T, infer E> ? E : never;
// ==========================================
// 2. Ok 클래스
// ==========================================
export class Ok<T, E = never> {
readonly _tag = 'ok' as const;
constructor(public readonly value: T) {}
isOk(): this is Ok<T, E> { return true; }
isErr(): this is Err<E, T> { return false; }
map<U>(fn: (val: T) => U): Result<U, E> {
return new Ok(fn(this.value));
}
mapErr<F>(_fn: (err: E) => F): Result<T, F> {
// E는 never이므로 새로운 Err를 생성할 필요 없이 Ok 그대로 반환해도 타입이 Result<T, F>로 안전하게 좁혀짐
return new Ok(this.value);
}
andThen<U, F>(fn: (val: T) => Result<U, F>): Result<U, E | F> {
return fn(this.value);
}
orElse<F>(_fn: (err: E) => Result<T, F>): Result<T, F> {
return new Ok(this.value);
}
unwrapOr(_defaultVal: T): T {
return this.value;
}
}
// =================…채점 근거전 요건+infer/never/conditional+any 회피 명시+AsyncResult
Gemma 4 12BGoogle808084607878
TypeScript 5.9(및 최신 버전)의 강력한 타입 시스템을 활용하여 Rust 스타일의 `Result` 패턴을 구현해 보겠습니다.
이 구현의 핵심은 **Error 타입의 Union 자동 누적**입니다. `andThen`을 사용할 때 이전 단계의 에러 타입과 다음 단계의 에러 타입이 합쳐지도록 설계했습니다.
### 1. 핵심 타입 및 클래스 정의
```typescript
/**
* (1) Discriminated Union 정의
*/
export type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
/**
* Result 객체를 생성하는 헬퍼
*/
export const ok = <T>(value: T): Result<T, never> => ({ ok: true, value });
export const err = <E>(error: E): Result<never, E> => ({ ok: false, error });
/**
* (2) Result 체이닝 유틸리티
*/
export const Result = {
// T -> U 변환 (에러는 그대로 유지)
map: <T, E, U>(res: Result<T, E>, fn: (value: T) => U): Result<U, E> =>
res.ok ? ok(fn(res.value)) : err(res.error),
// E -> E' 변환 (성공값은 그대로 유지)
mapErr: <T, E, E2>(res: Result<T, E>, fn: (error: E) => E2): Result<T, E2> =>
res.ok ? res : err(fn(res.error)),
/**
* (4) 타입 추론 핵심: Error 타입이 Union으로 누적됨
* T -> Result<U, E2>를 받아 Result<U, E | E2>를 반환
*/
andThen: <T, E, U, E2>(
res: Result<T, E>,
fn: (value: T) => Result<U, E2>
): Result<U, E | E2> => {
if (res.ok) {
const next = fn(res.value);
return next.ok ? next : err(next.error);
}
return err(res.error);
},
// 다른 Result로 대체 (에러 발생 시에만 실행)
orElse: <T, E, E2>(
res: Result<T, E>,
fn: () => Result<T, E2>
): Result<T, E | E2> => {
return res.ok ? res : fn();
},
// 에러 시 기본값 반환
unwrapOr: <T, E>(res: Result<T, E>, defaultValue: T): T =>
res.ok ? …채점 근거TS Result+체이닝: ★핵심 변별인 andThen 에러 union 누적 `Result<U, E | E2>` 정확 추론(rubric #1 g신호, DatabaseError|ValidationError 예시 시연) + ok/err를 Result<T,never>/Result<never,E>로 + AsyncResult=Promise<Result> map/andThen 누적 + ★부적합 케이스(단순 UI=over-engineering) 한계 명시(c). 단 ★함수형 설계(Result.andThen(res,fn))인데 예시2가 `Result.map(...).mapErr(...)` fluent 체이닝으로 작성 — namespace함수≠인스턴스메서드라 실제 타입/런타임 버그(페르소나는 .map().andThen() 유창 체이닝 원함) + fp-ts/neverthrow 라이브러리 비교 누락(s) + infer/conditional 미활용. 형제 26b(79) 수준, 31b(64)·qwen3.5(63) 명확 상회.
Qwen 3.7 MaxAlibaba808080808080
TypeScript 5.9 환경에서 Rust의 `Result<T, E>` 철학을 반영한 강력한 타입 안전성 및 체이닝 유틸리티를 설계했습니다.
요청하신 **Discriminated Union**, **에러 타입 Union 자동 누적**, **`never`/`infer` 활용**, 그리고 **`any` 배제**를 모두 만족하도록 구현했습니다.
---
### 1. 핵심 타입 및 구현 (Sync & Async)
```typescript
// ==========================================
// 1. 타입 정의 및 Discriminated Union
// ==========================================
export interface BaseResult<T, E> {
map<U>(fn: (val: T) => U): Result<U, E>;
mapErr<F>(fn: (err: E) => F): Result<T, F>;
andThen<U, F>(fn: (val: T) => Result<U, F>): Result<U, E | F>;
orElse<U, F>(fn: (err: E) => Result<U, F>): Result<T | U, F>;
unwrapOr<D>(defaultVal: D): T | D;
isOk(): this is Ok<T, E>;
isErr(): this is Err<T, E>;
}
export interface Ok<T, E> extends BaseResult<T, E> {
readonly tag: 'ok';
readonly value: T;
}
export interface Err<T, E> extends BaseResult<T, E> {
readonly tag: 'err';
readonly error: E;
}
export type Result<T, E> = Ok<T, E> | Err<T, E>;
// `infer`를 활용한 타입 추출 유틸리티
export type InferOk<R> = R extends Result<infer T, unknown> ? T : never;
export type InferErr<R> = R extends Result<unknown, infer E> ? E : never;
// ==========================================
// 2. 구현체 (내부 클래스) 및 팩토리 함수
// ==========================================
abstract class ResultImpl<T, E> implements BaseResult<T, E> {
abstract readonly tag: 'ok' | 'err';
abstract isOk(): this is Ok<T, E>;
abstract isErr(): this is Err<T, E>;
map<U>(fn: (val: T) => U): Result<U, E> {
r…채점 근거전 요건+infer/never+AsyncResult+error union 누적. 견고
Grok 4.3xAI707070707070
```typescript
// ─────────────────────────────────────────────────────────────────────────────
// Core Types
// ─────────────────────────────────────────────────────────────────────────────
type Ok<T> = { readonly tag: 'Ok'; readonly value: T };
type Err<E> = { readonly tag: 'Err'; readonly error: E };
type Result<T, E> = Ok<T> | Err<E>;
const Ok = <T>(value: T): Ok<T> => ({ tag: 'Ok', value });
const Err = <E>(error: E): Err<E> => ({ tag: 'Err', error });
// ─────────────────────────────────────────────────────────────────────────────
// Sync Result (method chaining via prototype augmentation)
// ─────────────────────────────────────────────────────────────────────────────
interface ResultMethods<T, E> {
map<U>(fn: (v: T) => U): Result<U, E>;
mapErr<F>(fn: (e: E) => F): Result<T, F>;
andThen<U, F>(fn: (v: T) => Result<U, F>): Result<U, E | F>;
orElse<U, F>(fn: (e: E) => Result<U, F>): Result<T | U, F>;
unwrapOr(defaultValue: T): T;
}
declare const ResultMethods: unique symbol;
type ResultWithMethods<T, E> = Result<T, E> & {
readonly [ResultMethods]: ResultMethods<T, E>;
};
// Factory that attaches methods
function result<T, E>(r: Result<T, E>): ResultWithMethods<T, E> {
const methods: ResultMethods<T, E> = {
map<U>(fn: (v: T) => U): ResultWithMethods<U, E> {
return result(
r.tag === 'Ok' ? Ok(fn(r.value)) : (r as Err<E>)
) as ResultWithMethods<U, E>;
},
mapErr<F>(fn: (e: E) => F): ResultWithMethods<T, F> {
return resul…채점 근거andThen+error union+AsyncResult 있으나 ★라이브러리 비교(fp-ts/neverthrow) 누락+infer 미활용. 4.4KB 빈약 [n=3] infer 활용 2/3로 개선(n=1 미활용). 라이브러리 비교는 3/3 누락(일관 한계).
Mistral Small 4Mistral808080708079
# Rust-Style `Result<T, E>` with Chaining in TypeScript 5.9
아래는 TypeScript 5.9에서 Rust 스타일의 `Result<T, E>` 타입과 체이닝 유틸리티를 구현한 해결책입니다. 타입 안전성과 체이닝 기능을 제공하며, `never`/`unknown`의 과도한 사용을 피하고 `infer`와 conditional type을 적극 활용했습니다.
```typescript
// ======================
// 1. 기본 Result 타입 정의
// ======================
type Ok<T> = { ok: true; value: T };
type Err<E> = { ok: false; error: E };
type Result<T, E> = Ok<T> | Err<E>;
// ======================
// 2. 메서드 인터페이스 (메서드 시그니처 선언)
// ======================
interface Result<T, E> {
// (2-a) OkVariant에만 적용되는 값 매핑
map<U>(this: Ok<T>, f: (value: T) => U): Result<U, E>;
// (2-b) ErrVariant에선 원본 반환 (타입만 Result<U,E>로 변환)
map<U>(this: Err<E>, _f: (value: T) => U): Result<U, E>;
// (2-c) 에러 매핑 (ErrVariant에만 적용)
mapErr<F>(this: Ok<T>, _f: (error: E) => F): Result<T, F>;
mapErr<F>(this: Err<E>, f: (error: E) => F): Result<T, F>;
// (2-d) 플랫매핑 (andThen) - 에러 타입 자동 누적 (요구사항 4)
andThen<U, E2>(this: Ok<T>, f: (value: T) => Result<U, E2>): Result<U, E | E2>;
andThen<U, E2>(this: Err<E>, _f: (value: T) => Result<U, E2>): Result<U, E>;
// (2-e) 에러 대체 (orElse) - 에러 타입 자동 누적
orElse<F>(this: Ok<T>, _f: (error: E) => Result<T, F>): Result<T, E>;
orElse<F>(this: Err<E>, f: (error: E) => Result<T, F>): Result<T, E | F>;
// (2-f) 기본값 반환
unwrapOr(this: Ok<T>, defaultValue: T): T;
unwrapOr(this: Err<E>, defaultValue: T): T;
}
// ======================
// 3. AsyncResult (Promise<Result<T,E>>) 지원
// ====================…채점 근거discriminated union+andThen+error union+AsyncResult+라이브러리 비교+infer
Step 3.7 FlashStepFun689082709081
### TypeScript 5.9 Rust 스타일 Result 타입 & 체이닝 유틸리티
요구사항을 모두 만족하도록 불변성 보장, 엄격한 타입 추론, 비동기 체이닝 지원을 포함해 구현했습니다. `any`를 사용하지 않고 `never`/`infer`/조건부 타입을 적극 활용했습니다.
---
#### 1. 핵심 타입 정의 & 헬퍼 함수
```typescript
/** discriminated union으로 정의된 Result 타입 (ok/err로 구분) */
export type Result<T, E> =
| { readonly ok: true; readonly value: T }
| { readonly ok: false; readonly error: E };
/** 성공 값을 생성하는 헬퍼 (에러 타입을 never로 고정해 타입 안전성 보장) */
export function ok<T>(value: T): Result<T, never> {
return { ok: true, value } as const satisfies Result<T, never>;
}
/** 실패 값을 생성하는 헬퍼 (성공 타입을 never로 고정해 타입 안전성 보장) */
export function err<E>(error: E): Result<never, E> {
return { ok: false, error } as const satisfies Result<never, E>;
}
/* infer + 조건부 타입을 활용한 타입 유틸리티 */
/** Result에서 성공 값 타입 추출 */
export type ResultValue<R extends Result<any, any>> =
R extends { ok: true; value: infer V } ? V : never;
/** Result에서 에러 타입 추출 */
export type ResultError<R extends Result<any, any>> =
R extends { ok: false; error: infer E } ? E : never;
```
---
#### 2. 동기 체이닝 유틸리티
`andThen` 체이닝 시 에러 타입이 자동으로 `union`으로 누적되도록 타입을 설계했습니다.
```typescript
/** 성공 시 value를 변환 (에러 타입 유지) */
export function map<T, E, U>(result: Result<T, E>, fn: (value: T) => U): Result<U, E> {
return result.ok ? ok(fn(result.value)) : result;
}
/** 실패 시 error를 변환 (성공 타입 유지) */
export function mapErr<T, E, F>(result: Result<T, E>, fn: (error: E) => F): Result<T, F> {
return result.ok ? result : err(fn(result.error));
}
/** flatMap: 성공 시 다음 Result를 체이닝 (에러 타입 자동 누적) */
export function andThen<T, E, U, F>(
result: Result<T, E>,
fn: (value: T) => Result<U, F>
): Result<U, E | F> {
return result.ok ? fn(result.value) : result;
}
/** 실패 시 복구 로직 실행 (에러 타입 자동 누적) */
export function orElse<T, E, F>(
result: Result<T, E>,
fn: (error: E) => Result<T, F>
): Result<T, E | F> {
return result.ok ? result : fn(result.error);
}
/** Result가 실패하면 기본값 반환 */
export function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T {
return result.ok ? result.value : defaultValue;
}
```
---
#### 3. 비동기 체이닝 유틸리티 (`AsyncResult`)
`Promise<Result<T, E>>`를 입력으로 받아 동기 유틸리티와 동일한 패턴으로 비동기 체이닝을 지원합니다.
```typescript
/** 비동기 성공 값 변환 */
export async function asyncMap<T, E, U>(
promiseResult: Promise<Result<T, E>>,
fn: (value: T) => Promise<U>
): Promise<Result<U, E>> {
const result = await promiseResult;
return result.ok ? ok(await fn(result.value)) : result;
}
/** 비동기 실패 값 변환 */
export async function asyncMapErr<T, E, F>(
promiseResult: Promise<Result<T, E>>,
fn: (error: E) => Promise<F>
): Promise<Result<T, F>> {
const result = await promiseResult;
return result.ok ? result : err(await fn(result.error));
}
/** 비동기 flatMap (에러 타입 자동 누적) */
export async function asyncAndThen<T, E, U, F>(
promiseResult: Promise<Result<T, E>>,
fn: (value: T) => Promise<Result<U, F>>
): Promise<Result<U, E | F>> {
const result = await promiseResult;
return result.ok ? await fn(result.value) : result;
}
/** 비동기 실패 복구 (에러 타입 자동 누적) */
export async function asyncOrElse<T, E, F>(
promiseResult: Promise<Result<T, E>>,
fn: (error: E) => Promise<Result<T, F>>
): Promise<Result<T, E | F>> {
const result = await promiseResult;
return result.ok ? result : await fn(result.error);
}
/** 비동기 결과 unwrap */
export async function asyncUnwrapOr<T, E>(promiseResult: Promise<Result<T, E>>, defaultValue: T): Promise<T> {
const result = await promiseResult;
return result.ok ? result.value : defaultValue;
}
```
---
#### 4. 사용 예제 3가지
##### 예제 1: 간단한 유효성 검사 체이닝
```typescript
type User = { name: string; age: number };
function validateName(name: string): Result<string, "NAME_EMPTY"> {
return name.trim() !== "" ? ok(name) : err("NAME_EMPTY");
}
function validateAge(age: number): Result<number, "AGE_INVALID"> {
return age >= 0 && age <= 120 ? ok(age) : err("AGE_INVALID");
}
const nameInput = " Alice ";
const ageInput = 30;
// map + andThen으로 User 생성 체이닝
const userResult = andThen(
map(validateName(nameInput), n => n.trim()),
trimmedName => map(validateAge(ageInput), age => ({ name: trimmedName, age } as User))
);
// 타입 추론: Result<User, "NAME_EMPTY" | "AGE_INVALID">
const user = unwrapOr(userResult, { name: "Guest", age: 0 });
console.log(user); // { name: "Alice", age: 30 }
```
##### 예제 2: 에러 타입 자동 누적 확인
```typescript
type MathError = "DIVISION_BY_ZERO" | "NEGATIVE_RESULT";
function divide(a: number, b: number): Result<number, "DIVISION_BY_ZERO"> {
return b !== 0 ? ok(a / b) : err("DIVISION_BY_ZERO");
}
function checkPositive(n: number): Result<number, "NEGATIVE_RESULT"> {
return n >= 0 ? ok(n) : err("NEGATIVE_RESULT");
}
// andThen 체이닝 시 에러 타입이 자동으로 union 누적
const step1 = divide(10, 2); // Result<number, "DIVISION_BY_ZERO">
const step2 = andThen(step1, checkPositive);
// 타입 추론: Result<number, "DIVISION_BY_ZERO" | "NEGATIVE_RESULT">
// 타입 유틸리티로 에러 타입 확인
type Step2Error = ResultError<typeof step2>; // "DIVISION_BY_ZERO" | "NEGATIVE_RESULT"
// orElse로 부분 복구
const recovered = orElse(step2, e =>
e === "DIVISION_BY_ZERO" ? ok(0) : err("CANNOT_RECOVER")
);
// 타입 추론: Result<number, "NEGATIVE_RESULT" | "CANNOT_RECOVER">
```
##### 예제 3: AsyncResult로 비동기 API 체이닝
```typescript
type Post = { id: number; title: string; authorId: number };
type Author = { id: number; name: string };
// 가짜 API 함수
async function fetchPost(id: number): AsyncResult<Post, Error> {
const posts: Post[] = [{ id: 1, title: "TypeScript Tips", authorId: 1 }];
return posts.find(p => p.id === id) ? ok(posts[0]) : err(new Error("Post not found"));
}
async function fetchAuthor(id: number): AsyncResult<Author, Error> {
const authors: Author[] = [{ id: 1, name: "Alice" }];
return authors.find(a => a.id === id) ? ok(authors[0]) : err(new Error("Author not found"));
}
// 비동기 체이닝: 게시글 → 작성자 이름 조회
async function getPostAuthorName(postId: number): AsyncResult<string, Error> {
return await asyncAndThen(
await fetchPost(postId),
post => asyncMap(fetchAuthor(post.authorId), author => author.name)
);
}
const authorName = await asyncUnwrapOr(await getPostAuthorName(1), "Unknown");
console.log(authorName); // "Alice"
```
---
#### 5. Result 패턴이 부적합한 케이스
> **여러 검증 에러를 동시에 수집해야 하는 경우**
> Result는 단일 에러만 표현할 수 있어, 모든 검증 실패를 한 번에 모아서 사용자에게 보여줘야 하는 폼 유효성 검사 등에는 부적합합니다.
```typescript
type FormErrors = { name?: string; email?: string; age?: string };
type FormData = { name: string; email: string; age: number };
// 각 필드별 검증 함수
function validateName(name: string): Result<void, string> {
return name.length >= 2 ? ok(undefined) : err("Name must be ≥2 chars");
}
// ❌ Result 체이닝은 첫 번째 에러에서 중단되므로 모든 에러를 수집할 수 없음
const badForm = { name: "A", email: "invalid", age: 17 };
const validateResult = andThen(
validateName(badForm.name),
() => andThen(
() => validateEmail(badForm.email), // 실행되지 않음
() => validateAge(badForm.age) // 실행되지 않음
)
);
// 결과: Result<void, "Name must be ≥2 chars"> (나머지 에러 손실)
// ✅ 적합한 해결책: 에러를 배열로 수집하는 별도의 Validation 타입 사용
type ValidationResult<T, E> = { ok: true; value: T } | { ok: false; errors: E[] };
function validateAll(form: FormData): ValidationResult<FormData, string> {
const errors: string[] = [];
if (form.name.length < 2) errors.push("Name must be ≥2 chars");
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) errors.push("Invalid email");
if (form.age < 18) errors.push("Must be ≥18");
return errors.length === 0 ? ok(form) : err(errors);
}
const allErrors = validateAll(badForm);
if (!allErrors.ok) console.log(allErrors.errors);
// ["Name must be ≥2 chars", "Invalid email", "Must be ≥18"] (모든 에러 수집)
```
---
### 주요 특징
1. **타입 안전성**: `ok`/`err` 헬퍼로 생성한 Result는 불필요한 에러/성공 값 접근을 타입 단계에서 차단합니다.
2. **자동 에러 누적**: `andThen`/`orElse` 체이닝 시 에러 타입이 자동으로 `union`으로 합쳐져 타입 추론이 정확합니다.
3. **비동기 지원**: `AsyncResult` 함수로 비동기 흐름도 동기와 동일한 패턴으로 체이닝할 수 있습니다.
4. **불변성**: 모든 Result 객체는 readonly로 생성되어 사이드 이펙트를 방지합니다.채점 근거Rust식 Result discriminated union + ok/err(never 고정) + infer/조건부 ResultValue/Error + sync 체이닝(map/andThen/orElse) + async(Promise<Result>) + ★andThen 에러타입 union 자동 누적 + ★Result 부적합 케이스(검증에러 동시수집→Validation 배열) 통찰 우수. 단 ★예제서 쓰는 `AsyncResult<T,E>` 타입 미정의(컴파일 오류) + 예제3 await 후 Promise 기대 시그니처 약간 mismatch.
Qwen 3.6 27BAlibaba808080708079
# TypeScript 5.9 `Result<T, E>` & `AsyncResult` 구현
Rust 스타일의 `Result` 패턴을 TypeScript에 맞게 타입 안전하게 구현했습니다. `any`/`unknown`을 배제하고 `never`, 조건부 타입, discriminated union을 활용하여 컴파일러가 unreachable branch를 정확히 추론하도록 설계했습니다.
```typescript
// ============================================================================
// 1. Discriminated Union 정의
// ============================================================================
type Ok<T> = { ok: true; value: T };
type Err<E> = { ok: false; error: E };
type Result<T, E> = Ok<T> | Err<E>;
// ============================================================================
// 2. Result<T, E> 인터페이스 & 팩토리
// ============================================================================
interface ResultType<T, E> extends Result<T, E> {
isOk(): this is Ok<T>;
isErr(): this is Err<E>;
map<U>(fn: (value: T) => U): ResultType<U, E>;
mapErr<F>(fn: (error: E) => F): ResultType<T, F>;
andThen<U, F>(fn: (value: T) => ResultType<U, F>): ResultType<U, E | F>;
orElse<F>(fn: (error: E) => ResultType<T, F>): ResultType<T, F>;
unwrapOr(default: T): T;
}
export function ok<T>(value: T): ResultType<T, never> {
return {
ok: true,
value,
isOk: () => true,
isErr: () => false,
map: <U>(fn: (v: T) => U) => ok(fn(value)),
// unreachable branch: error 타입이 never이므로 fn은 호출되지 않음
mapErr: <F>(fn: (e: never) => F) => ok(value) as ResultType<T, F>,
andThen: <U, F>(fn: (v: T) => ResultType<U, F>) => fn(value),
orElse: <F>(fn:…채점 근거discriminated union+andThen+error union+AsyncResult+라이브러리 비교
Gemini 3.1 Flash LiteGoogle808080708079
TypeScript 5.9의 강력한 타입 추론 기능을 활용하여, Rust의 `Result` 패턴을 재현한 구현체입니다.
### 1. Result 타입 정의 및 구현
```typescript
type Ok<T> = { readonly tag: 'ok'; readonly value: T };
type Err<E> = { readonly tag: 'err'; readonly error: E };
type ResultUnion<T, E> = Ok<T> | Err<E>;
class Result<T, E> {
constructor(private readonly val: ResultUnion<T, E>) {}
static ok<T, E = never>(value: T): Result<T, E> {
return new Result({ tag: 'ok', value });
}
static err<T = never, E = unknown>(error: E): Result<T, E> {
return new Result({ tag: 'err', error });
}
// (2) 메서드 구현
map<U>(fn: (val: T) => U): Result<U, E> {
return this.val.tag === 'ok'
? Result.ok(fn(this.val.value))
: Result.err(this.val.error);
}
mapErr<F>(fn: (err: E) => F): Result<T, F> {
return this.val.tag === 'err'
? Result.err(fn(this.val.error))
: Result.ok(this.val.value);
}
andThen<U, E2>(fn: (val: T) => Result<U, E2>): Result<U, E | E2> {
return this.val.tag === 'ok'
? fn(this.val.value)
: Result.err(this.val.error);
}
orElse<T2, E2>(fn: (err: E) => Result<T2, E2>): Result<T | T2, E2> {
return this.val.tag === 'err'
? fn(this.val.error)
: Result.ok(this.val.value);
}
unwrapOr(fallback: T): T {
return this.val.tag === 'ok' ? this.val.value : fallback;
}
}
```
### 2. AsyncResult (Promise wrapper)
```typescript
class AsyncResult<T, E> {
constructor(private readonly promise: Promise<Result<T, E>>) {}
async map…채점 근거class기반 Ok/Err+andThen+error union 누적+AsyncResult+라이브러리 비교. 견고
Gemma 4 26B A4BGoogle808080708079
TypeScript 5.x의 강력한 타입 추론 기능을 활용하여, Rust의 `Result` 패턴을 구현했습니다. 특히 `andThen` 체이닝 시 에러 타입이 자동으로 합집합(`Union`)되는 기능에 초점을 맞췄습니다.
### 1. Result & AsyncResult 구현
```typescript
/**
* (1) Result Discriminated Union 정의
*/
export type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
/**
* 유틸리티 클래스: Result를 래핑하여 메서드 체이닝 제공
*/
export class ResultWrapper<T, E> {
constructor(private readonly _result: Result<T, E>) {}
// Getter로 내부 값 접근
get value(): Result<T, E> { return this._result; }
// (2) 핵심 메서드 구현
// 성공 시 값 변환
map<U>(fn: (val: T) => U): ResultWrapper<U, E> {
return this._result.ok
? new ResultWrapper<U, E>({ ok: true, value: fn(this._result.value) })
: new ResultWrapper<U, E>({ ok: false, error: this._result.error });
}
// 에러 발생 시 에러 값 변환
mapErr<F>(fn: (err: E) => F): ResultWrapper<T, F> {
return this._result.ok
? new ResultWrapper<T, F>({ ok: true, value: this._result.value })
: new ResultWrapper<T, F>({ ok: false, error: fn(this._result.error) });
}
// (4) andThen: 성공 시 다음 Result 반환 (에러 타입 자동 누적)
andThen<U, F>(fn: (val: T) => Result<U, F>): ResultWrapper<U, E | F> {
if (this._result.ok) {
const next = fn(this._result.value);
return new ResultWrapper<U, E | F>(next);
}
// 에러 발생 시 기존 에러를 유지하되, 반환 타입의 에러 유니온에 맞춤
return new ResultWrapper<U, E | F>({ ok: false, error: this._result.error as E | F });
}
// 에러 시 다른 Result로 복구
orElse<F>(fn: (err: E) => Result<T, F…채점 근거discriminated union+andThen+error union+AsyncResult+라이브러리 비교
Qwen 3.6 35B A3BAlibaba808080708079
TypeScript 5.9의 향상된 제네릭 추론, `satisfies` 연산자, 조건부 타입 능력을 고려하여 설계한 Rust 스타일 `Result` 패턴입니다. `any`를 배제하고 `never`, 조건부 타입, 불변성 패턴을 활용했습니다.
### 1. 핵심 구현 (`Result<T, E>` & `AsyncResult<T, E>`)
```typescript
// (1) Discriminated Union 정의
type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
// (2) Result 클래스: 메서드 체이닝 & 타입 추론
class Result<T, E> {
private constructor(private readonly _data: Result<T, E>) {}
static ok<T, E = never>(value: T): Result<T, E> {
return new Result({ ok: true, value });
}
static err<T = never, E>(error: E): Result<T, E> {
return new Result({ ok: false, error });
}
get ok(): this['ok'] { return this._data.ok; }
get value(): T {
if (!this.ok) throw new Error('Called `value` on an Err');
return this._data.value as T;
}
get error(): E {
if (this.ok) throw new Error('Called `error` on an Ok');
return this._data.error as E;
}
map<U>(f: (t: T) => U): Result<U, E> {
return this.ok ? Result.ok<U, E>(f(this.value)) : Result.err<U, E>(this._data.error as E);
}
mapErr<F>(f: (e: E) => F): Result<T, F> {
return this.ok ? Result.ok<T, F>(this.value) : Result.err<T, F>(f(this.error));
}
// (4) error 타입 자동 누적: E | F
andThen<U, F>(f: (t: T) => Result<U, F>): Result<U, E | F> {
return this.ok
? (f(this.value) as Result<U, E | F>)
: Result.err<U, E | F>(this._data.error as E);
}
orElse<F>(f: (e: E) => Result<T, F>): Result<T, E | F> {
return this.ok
? Res…채점 근거discriminated union+andThen+error union+AsyncResult+라이브러리 비교+infer. 완전
Solar Pro 3Upstage808060408576
아래 예시는 **TypeScript 5.9**(TS 5.9 이상의 최신 타입 연산자를 활용)에서
Rust‑style `Result<T, E>` 를 **discriminated union** 으로 정의하고,
`map`, `mapErr`, `andThen`, `orElse`, `unwrapOr` 와 같은 체이닝 헬퍼 메서드를 구현한 **`Result<T, E>`** 와
비동기 컨텍스트에서도 동일하게 사용할 수 있는 **`AsyncResult<T, E>`** 를 보여줍니다.
---
## 1️⃣ `Result<T, E>` 정의
```ts
// ---------------------------------------------------------------
// 1️⃣ Result<T, E> discriminated union
// ---------------------------------------------------------------
export type Result<T, E> =
| { readonly _tag: "ok"; readonly value: T }
| { readonly _tag: "err"; readonly error: E };
```
* `_tag: "ok"` / `_tag: "err"` 로 구분된 **union**이며,
`readonly` 로 불변성을 명시했습니다.
* `T` 와 `E` 은 각각 **성공값**과 **실패값** 타입이며,
`never` 를 이용해 “절대로 발생하지 않는다”는 상황을 표현할 수 있습니다.
---
## 2️⃣ 기본 헬퍼 (Result 전용)
```ts
// ---------------------------------------------------------------
// 2️⃣ 헬퍼 : map / mapErr / andThen / orElse / unwrapOr
// ---------------------------------------------------------------
/** 성공값을 변형 */
export function map<T, U, E>(r: Result<T, E>, f: (v: T) => U): Result<U, E> {
switch (r._tag) {
case "ok": return { _tag: "ok", value: f(r.value) };
case "err": return r;
}
}
/** 실패값을 변형 */
export function mapErr<T, E, F>(r: Result<T, E>, f: (e: E) => F): Result<T, F> {
switch (r._tag) {
case "ok": return r;
case "err": return { _tag: "err", error: f(r.error) };
}
}
/** 성공이면 다음 함수를 바로 호출, 실패이면 그대로 반환 */
export function andThen<T, U, E1, E2>(
r: Result<T, E1>,
f: (v: T) => Result<U, E2>
): Result<U, E1 | E2> {
switch (r._tag) {
case "ok": return f(r.value);
case "err": return { _tag: "err", error: r.error };
}
}
/** 실패이면 대체값을 반환 */
export function orElse<T, U, E>(r: Result<T, E>, f: () => Result<U, E>): Result<U, E> {
switch (r._tag) {
case "ok": return r;
case "err": return f();
}
}
/** 실패이면 기본값을 반환 */
export function unwrapOr<T, E>(r: Result<T, E>, defaultValue: T): T {
return r._tag === "ok" ? r.value : defaultValue;
}
```
### 핵심 타입 추론 포인트
* `andThen` 의 반환 타입은 **`E1 | E2`** 로 자동 누적됩니다.
이는 **union type inference** (`E1 extends any, E2 extends any`) 로 구현했으며,
`infer`·`conditional` 타입을 쓰지 않아도 TS가 충분히 추론합니다.
* `mapErr` 은 오류 타입만 바꾸므로 **`E` → `F`** 로 바뀝니다.
* `orElse` 가 `Result<U, E>` 를 반환하므로, `Result<T, E>` 와 자유롭게 이어붙일 수 있습니다.
---
## 3️⃣ 비동기용 `AsyncResult<T, E>`
```ts
// ---------------------------------------------------------------
// 3️⃣ AsyncResult<T, E> – Promise<Result<T, E>> 를 감싸는 타입
// ---------------------------------------------------------------
export type AsyncResult<T, E> = Promise<Result<T, E>>;
```
`Promise` 가 이미 `Result` 를 감싸고 있기 때문에 `Result` 와 동일한
메서드 시그니처를 재사용하면 됩니다. 아래는 간단히 **헬퍼들을 AsyncResult 에 맞게
재포장**한 예시입니다.
```ts
export function asyncMap<T, U, E>(
ar: AsyncResult<T, E>,
f: (v: T) => U
): AsyncResult<U, E> {
return ar.then(r => map(r, f));
}
export function asyncMapErr<T, E, F>(
ar: AsyncResult<T, E>,
f: (e: E) => F
): AsyncResult<T, F> {
return ar.then(r => mapErr(r, f));
}
export function asyncAndThen<T, U, E1, E2>(
ar: AsyncResult<T, E1>,
f: (v: T) => AsyncResult<U, E2>
): AsyncResult<U, E1 | E2> {
return ar.then(r => andThen(r, v => f(v)));
}
export function asyncOrElse<T, U, E>(
ar: AsyncResult<T, E>,
f: () => AsyncResult<U, E>
): AsyncResult<U, E> {
return ar.then(r => orElse(r, f));
}
export function asyncUnwrapOr<T, E>(ar: AsyncResult<T, E>, def: T): Promise<T> {
return ar.then(r => unwrapOr(r, def));
}
```
> **Tip** – 실제 프로젝트에서는 `AsyncResult` 를 `Result` 와 동일하게
> `map`, `mapErr`, … 로 **오버로드**하거나, `Result` 를 `AsyncResult` 로
> **상속**(`type AsyncResult<T, E> = Promise<Result<T, E>>`) 해도 됩니다.
> 여기서는 가독성을 위해 별도 함수만 제공했습니다.
---
## 4️⃣ 사용 예시 3가지
### 4‑1️⃣ 순수 동기 `Result`
```ts
// 간단한 파싱 함수
function parseIntSafe(str: string): Result<number, string> {
const n = Number(str);
return Number.isNaN(n) ? { _tag: "err", error: "Not a number" } : { _tag: "ok", value: n };
}
// 체이닝 예시
const r = parseIntSafe("42")
.map(v => v * 2) // ok → ok
.mapErr(e => `${e} (mapped)`)
.andThen(v => v > 0 ? { _tag: "ok", value: v } : { _tag: "err", error: "Non‑positive" });
console.log(r); // { _tag: "ok", value: 84 }
// unwrapOr 로 기본값 얻기
const defaultVal = parseIntSafe("abc").unwrapOr(0); // 0
```
### 4‑2️⃣ 여러 단계 `andThen` 에서 에러 타입이 누적되는 경우
```ts
type FirstError = { kind: "first"; reason: string };
type SecondError = { kind: "second"; reason: number };
function step1(): Result<string, FirstError> {
return Math.random() < 0.7 ? { _tag: "ok", value: "hello" } : { _tag: "err", error: { kind: "first", reason: "boom" } };
}
function step2(s: string): Result<number, SecondError> {
return Math.random() < 0.5 ? { _tag: "ok", value: s.length } : { _tag: "err", error: { kind: "second", reason: 42 } };
}
function step3(n: number): Result<boolean, string> {
return n > 3 ? { _tag: "ok", value: true } : { _tag: "err", error: "too small" };
}
// 에러 타입이 자동으로 FirstError | SecondError | string 으로 누적
const r = step1()
.andThen(step2)
.andThen(step3);
// 타입 확인 (컴파일러가 추론)
type R = typeof r; // Result<boolean, FirstError | SecondError | string>
```
### 4‑3️⃣ 비동기 `AsyncResult`
```ts
async function fetchUser(id: string): AsyncResult<User, string> {
const resp = await fetch(`/api/user/${id}`).then(r => r.json());
if (!resp.ok) return { _tag: "err", error: `HTTP ${resp.status}` };
// API 응답 형태가 {id, name} 이라고 가정
return { _tag: "ok", value: { id, name: resp.data.name } };
}
async function validateUser(u: User): AsyncResult<{ id: number; name: string }, ValidationError> {
const n = Number(u.id);
return Number.isNaN(n)
? { _tag: "err", error: new ValidationError("id is not numeric") }
: { _tag: "ok", value: { id: n, name: u.name } };
}
// 비동기 체이닝
async function getUserProfile(id: string) {
const result = await fetchUser(id) // AsyncResult<User, string>
.andThen(u => validateUser(u)) // AsyncResult<{id:number,name:string}, ValidationError | string>
.map(v => ({ ...v, createdAt: new Date() })); // AsyncResult<{id:number,name:string,createdAt:Date}, ...>
// 최종 unwrapOr 로 기본값 반환
const ok = await asyncUnwrapOr(result, { id: 0, name: "default", createdAt: new Date() });
console.log(ok);
}
```
> `asyncUnwrapOr` 은 `Promise<T>` 를 반환하므로 `await` 로 바로 사용할 수 있습니다.
---
## 5️⃣ `Result` 패턴이 부적합한 경우
| 상황 | 왜 부적합? | 대안 |
|------|-----------|------|
| **예외가 런타임에 복구 불가능**하고 **프로그램 전체를 중단**해야 하는 경우 (예: `null` 포인터 dereference, `undefined` 접근) | `Result` 은 **복구 가능한** 오류를 명시적으로 모델링하도록 설계되었습니다. 복구가 불가능하면 `throw` 로 예외를 던지는 것이 자연스럽고, `Result` 로 감싸면 불필요한 `ok/err` 체크가 늘어납니다. | 기존 `throw` / `try…catch` 를 사용. |
| **값이 존재하지 않을 가능성이 전혀 없고**, **옵션값**(`null` / `undefined`)이 **잘못된 입력**인 경우 | `Result` 은 `ok` 와 `err` 로 **정상 흐름**과 **오류 흐름**을 구분하지만, 옵션이 **전혀 허용되지 않을 때**는 `null` 체크가 오히려 더 직관적입니다. | `strictNullChecks` 를 켜고 `!` 연산자 혹은 `assert` 헬퍼 사용. |
| **다수의 서로 다른 오류 원인**이 **동일한 핸들러**에서 처리될 필요가 없고, **오류마다 전혀 다른 로직**(예: 파일 시스템 오류, 네트워크 오류, 파싱 오류) | `Result` 은 **단일 오류 타입**을 갖는 것이 전제이므로, `E` 가 `any` 혹은 `unknown` 으로 확장되면 의미가 퇴색합니다. 이런 경우 **예외 타입**을 구체적으로 정의하고 `try/catch` 로 분기하는 것이 더 깔끔합니다. | `Error` 서브클래스를 만든 뒤 `throw` 로 구분하고, `catch` 로 각각 처리. |
> 위 예시들은 **핵심 아이디어**를 보여주기 위한 것이며, 실제 프로젝트에서는
> `Result` 를 **전역 에러 처리 전략**에 맞춰 선택적으로 사용하면 됩니다.
---
## 6️⃣ 전체 코드 (복사‑붙여넣기용)
```ts
// ---------------------------------------------------------------
// Result.ts
// ---------------------------------------------------------------
export type Result<T, E> =
| { readonly _tag: "ok"; readonly value: T }
| { readonly _tag: "err"; readonly error: E };
export function map<T, U, E>(r: Result<T, E>, f: (v: T) => U): Result<U, E> {
switch (r._tag) {
case "ok": return { _tag: "ok", value: f(r.value) };
case "err": return r;
}
}
export function mapErr<T, E, F>(r: Result<T, E>, f: (e: E) => F): Result<T, F> {
switch (r._tag) {
case "ok": return r;
case "err": return { _tag: "err", error: f(r.error) };
}
}
export function andThen<T, U, E1, E2>(
r: Result<T, E1>,
f: (v: T) => Result<U, E2>
): Result<U, E1 | E2> {
switch (r._tag) {
case "ok": return f(r.value);
case "err": return { _tag: "err", error: r.error };
}
}
export function orElse<T, U, E>(r: Result<T, E>, f: () => Result<U, E>): Result<U, E> {
switch (r._tag) {
case "ok": return r;
case "err": return f();
}
}
export function unwrapOr<T, E>(r: Result<T, E>, defaultValue: T): T {
return r._tag === "ok" ? r.value : defaultValue;
}
// ---------------------------------------------------------------
// AsyncResult.ts
// ---------------------------------------------------------------
export type AsyncResult<T, E> = Promise<Result<T, E>>;
export function asyncMap<T, U, E>(ar: AsyncResult<T, E>, f: (v: T) => U): AsyncResult<U, E> {
return ar.then(r => map(r, f));
}
export function asyncMapErr<T, E, F>(ar: AsyncResult<T, E>, f: (e: E) => F): AsyncResult<T, F> {
return ar.then(r => mapErr(r, f));
}
export function asyncAndThen<T, U, E1, E2>(
ar: AsyncResult<T, E1>,
f: (v: T) => AsyncResult<U, E2>
): AsyncResult<U, E1 | E2> {
return ar.then(r => andThen(r, v => f(v)));
}
export function asyncOrElse<T, U, E>(
ar: AsyncResult<T, E>,
f: () => AsyncResult<U, E>
): AsyncResult<U, E> {
return ar.then(r => orElse(r, f));
}
export function asyncUnwrapOr<T, E>(ar: AsyncResult<T, E>, def: T): Promise<T> {
return ar.then(r => unwrapOr(r, def));
}
```
---
### 🎯 핵심 요약
| 기능 | 구현 포인트 |
|------|--------------|
| `Result<T, E>` discriminated union | `_tag: "ok"` / `_tag: "err"` |
| `map`, `mapErr` | 성공/실패 값만 변형 |
| `andThen` | 성공 시 다음 `Result` 로 전환, 오류 타입은 `E1 | E2` 로 누적 |
| `orElse` / `unwrapOr` | 실패 시 대체값 제공 |
| `AsyncResult<T, E>` | `Promise<Result<T, E>>` 를 감싸는 타입, 동기 메서드와 동일한 체이닝 지원 |
| 타입 추론 | `andThen` → 자동 누적, `map` → 값 변형만, `mapErr` → 오류 타입만 교체 |
| 부적합 케이스 | 복구 불가능 예외, 옵션이 전혀 허용되지 않을 때, 다양한 오류 로직을 별도 `throw` 로 처리하고 싶을 때 |
필요에 따라 `Result` 를 **전역 에러 핸들러** 혹은 **함수 반환 타입**에 활용하면
Rust‑style 의 **명시적 오류 흐름**을 JS/TS에서도 깔끔하게 구현할 수 있습니다. Happy coding! 🚀채점 근거Result<T,E> discriminated union(readonly _tag)·map/mapErr/andThen/orElse/unwrapOr 완비. 타입 정의 정밀.
Qwen 3.5 9BAlibaba487055707563
TypeScript 5.9 환경 (최근 최신 버전 기준) 에서 요구하신 Rust 스타일 `Result` 패턴을 구현해 보았습니다. `any` 를 배제하고, 타입 추론 (Inference), 조건부 타입 (Conditional Type), 그리고 `infer`를 활용하여 타입 안전성을 높였습니다.
### 1. Core Implementation
```typescript
// 1. Discriminated Union 정의
// ok: T, err: E
type Result<T, E> = { ok: T } | { err: E };
// --- Factory Functions ---
const ok = <T, E>(value: T): Result<T, E> => ({ ok: value });
const err = <T, E>(error: E): Result<T, E> => ({ err: error });
// --- Result Interface & Sync Methods ---
interface Result<T, E> {
// Map: 성공 시 값 변환, 실패 시값 유지
map<U>(fn: (val: T) => U): Result<U, E>;
// MapErr: 실패 시 에러 변환, 성공 시 값 유지
mapErr<F>(fn: (err: E) => F): Result<T, F>;
// AndThen (FlatMap): 성공 시 콜백 실행 후 그 결과로 이어짐
// 에러 타입 누적 (Union) 을 위한 Overload 사용
andThen<U>(fn: (val: T) => U): Result<U, E>;
andThen<U, F>(fn: (val: T) => Result<U, F>): Result<U, E | F>;
// OrElse: 실패 시 대체 함수 실행
orElse<F>(fn: (err: E) => Result<T, F>): Result<T, E | F>;
// UnwrapOr: 성공 값이 없으면 default 반환
unwrapOr(defaultValue: T): T;
}
// Implementation
const resultMap = <T, E>(r: Result<T, E>, fn: (val: T) => U): Result<U, E> => {
return 'ok' in r ? ok(fn(r.ok)) : r;
};
const resultMapErr = <T, E>(r: Result<T, E>, fn: (err: E) => F): Result<T, F> => {
return 'err' in r ? err(fn(r.err)) : r;
};
const resultAndThen = <T, E>(r: Result<T, E>, fn: (val: T) => U | Result<U, E> | Result<U, F>): Result<U, E | F> => {
if ('ok' in r) {
const res = fn(r.ok);
// Union narr…채점 근거TS Result 타입(no-any 명시 요구): 개념은 이해 — map/mapErr/andThen/orElse/unwrapOr 시맨틱 + andThen 에러타입 E|F union 누적(오버로드 시그니처 정확). 그러나 실행 결함 대량: (1) discriminant를 구조적 'ok' 키로 잡아 r.ok 트루시 체크→T가 0/false/''면 버그, 'ok' in r와 r.ok 혼용으로 최종본 컴파일 불가, (2) type Result와 interface Result 동명 선언병합 불가(TS 에러), (3) 제네릭 U/F 미선언 free 변수 다수, (4) AsyncResult.prototype.map=…→인터페이스엔 런타임 prototype 없음(근본 불가), (5) andThen 구현 as any로 처리—질문이 금지한 any를 정면 위반, (6) Example의 fetchUserData가 Result 미반환. 부적합 케이스는 1개(void/logging)만 빈약. T1 최약셀.
Gemma 4 31BGoogle608060606064
TypeScript 5.9(및 최신 5.x 버전)의 강력한 타입 시스템을 활용하여 Rust 스타일의 `Result` 패턴을 구현해 보겠습니다.
단순한 인터페이스가 아니라 **메서드 체이닝**을 지원하기 위해 클래스 기반의 구현과, `Promise`를 래핑한 `AsyncResult`를 함께 설계했습니다.
### 구현 코드
```typescript
/**
* Result 타입 정의
* Discriminated Union을 통해 ok와 err를 구분합니다.
*/
export type Result<T, E> = Ok<T, E> | Err<T, E>;
export class Ok<T, E> {
readonly type = 'ok' as const;
constructor(readonly value: T) {}
map<U>(fn: (value: T) => U): Result<U, E> {
return new Ok(fn(this.value));
}
mapErr<F>(fn: (err: E) => F): Result<T, F> {
return this; // Ok일 때는 에러 매핑이 일어나지 않음 (타입 캐스팅 필요)
}
// 에러 타입 union 누적의 핵심: E | E2
andThen<U, E2>(fn: (value: T) => Result<U, E2>): Result<U, E | E2> {
return fn(this.value);
}
orElse<T2>(fn: (err: E) => Result<T, E>): Result<T, E> {
return this;
}
unwrapOr(defaultValue: T): T {
return this.value;
}
}
export class Err<T, E> {
readonly type = 'err' as const;
constructor(readonly error: E) {}
map<U>(fn: (value: T) => U): Result<U, E> {
return this as unknown as Result<U, E>;
}
mapErr<F>(fn: (err: E) => F): Result<T, F> {
return new Err(fn(this.error));
}
andThen<U, E2>(fn: (value: T) => Result<U, E2>): Result<U, E | E2> {
return this as unknown as Result<U, E | E2>;
}
orElse<T2>(fn: (err: E) => Result<T, E>): Result<T, E> {
return fn(this.error);
}
unwrapOr(defaultValue: T): T {
return defaultValue;
}
}
// 팩토리 함수
export const result = {
ok: <T, E>(value: T): Re…채점 근거클래스 기반 error union 누적(andThen E|E2)은 달성하나 ★never/infer/conditional 미사용+orElse 타이핑 약함(에러변환 불가)+any 3회+최단·저밀도
EXAONE 4.5 33BLG AI808060408074
### TypeScript 5.9에서 Rust 스타일 `Result<T, E>` 구현
```typescript
// 1. Discriminated Union 정의 (ok/err)
type Result<T, E> =
| { kind: "ok"; value: T }
| { kind: "err"; error: E };
// 2. 핵심 메서드 구현 (타입 추론 최적화)
const ok = <T, E>(value: T): Result<T, E> => ({ kind: "ok", value });
const err = <T, E>(error: E): Result<T, E> => ({ kind: "err", error });
// map: 성공 시 값 변환
const map = <T, U, E>(result: Result<T, E>, fn: (value: T) => U): Result<U, E> =>
result.kind === "ok"
? ok(fn(result.value))
: result;
// mapErr: 실패 시 에러 변환
const mapErr = <T, E, E2>(result: Result<T, E>, fn: (error: E) => E2): Result<T, E2> =>
result.kind === "err"
? err(fn(result.error))
: result;
// andThen: 체이닝 시 오류 타입 자동 Union 누적 (핵심 요구사항 4)
const andThen = <T, U, E, E2>(
result: Result<T, E>,
fn: (value: T) => Result<U, E2>
): Result<U, E | E2> => {
if (result.kind === "ok") {
const newResult = fn(result.value);
// E2가 E | E2의 서브타입이므로 타입 추론 가능
return newResult as Result<U, E | E2>;
} else {
// 기존 에러는 E | E2의 서브타입 (E ⊆ E | E2)
return result as Result<U, E | E2>;
}
};
// orElse: 실패 시 대체 Result 반환
const orElse = <T, E, E2>(
result: Result<T, E>,
fn: () => Result<T, E2>
): Result<T, E | E2> =>
result.kind === "err"
? fn()
: result;
// unwrapOr: 실패 시 기본값 반환
const unwrapOr = <T, E>(result: Result<T, E>, defaultValue: T): T =>
result.kind === "ok" ? result.value : defaultValue;
// 3. Promise<Result<T, E>> 지원 (AsyncResult)
type AsyncResult<T, E> = Promise<Result<T, E>>;
const asyncMap = async <T, U, E>(
promiseResult: AsyncResult<T, E>,
fn: (value: T) => U
): Promise<Result<U, E>> => {
const result = await promiseResult;
return map(result, fn);
};
const asyncAndThen = async <T, U, E, E2>(
promiseResult: AsyncResult<T, E>,
fn: (value: T) => Result<U, E2>
): Promise<Result<U, E | E2>> => {
const result = await promiseResult;
return andThen(result, fn);
};
// 4. 타입 추론 최적화 예시 (andThen 체이닝 시 오류 타입 Union 자동 누적)
// - A | B | C 형태로 오류 타입이 자동 누적됨
type ParseError = "parse_failed";
type ValidationError = "validation_failed";
type NetworkError = "network_error";
const parseInput = (input: string): Result<number, ParseError> =>
input === "123" ? ok(123) : err("parse_failed");
const validateInput = (input: number): Result<number, ValidationError> =>
input > 0 ? ok(input) : err("validation_failed");
const fetchData = (): Result<string, NetworkError> =>
ok("data");
// 체이닝 시 오류 타입: ParseError | ValidationError | NetworkError
const result = parseInput("123")
.andThen(validateInput)
.andThen(() => fetchData());
// 5. 사용 예시 3가지
// (1) 간단한 값 변환
const double = ok(5).map(x => x * 2);
// Result<number, never> → { kind: "ok", value: 10 }
// (2) 체이닝 + 오류 누적
const process = parseInput("abc")
.andThen(validateInput)
.andThen(() => fetchData());
// 오류 타입: ParseError | ValidationError | NetworkError
// (3) AsyncResult 체이닝
const asyncProcess = async (): Promise<Result<string, string>> => {
return await asyncAndThen(
asyncAndThen(
parseInput("123"),
validateInput
),
() => fetchData()
);
};
// 6. Result 패턴이 부적합한 케이스
// - **동적 개수의 반환값이 필요할 때**
// Result는 단일 값(T)만 반환 가능하므로,
// 동적으로 변하는 개수의 데이터를 처리해야 할 때는 부적합합니다.
// 예: "사용자 목록 + 메타데이터"를 반환해야 할 때
// → 대신 `{ data: any[], metadata: any }` 같은 객체 구조 사용 필요
// (타입 안전성을 위해 조건부 타입/인덱스드 접근자 활용)
```
---
### 핵심 설계 설명
1. **Discriminated Union**
`kind: "ok"/"err"`로 타입 분기 → 타입 안정성 보장
2. **타입 추론 최적화**
- `andThen` 체이닝 시 **오류 타입 자동 Union** (`E | E2 | E3...`)
- `E2`가 `E | E2`의 서브타입이므로 타입 추론 가능
- `never` 타입을 명시적으로 사용하지 않고도 안전한 타입 확장
3. **AsyncResult 지원**
- `Promise<Result<T, E>>`에 동일한 메서드 적용
- `asyncAndThen`으로 체이닝 시 오류 타입 Union 유지
4. **과도한 `any`/`unknown` 회피**
- `never`는 반환 타입에서 안전히 사용 (예: `Result<number, never>`로 성공만 보장)
- 조건부 타입/인덱스드 접근자로 동적 데이터 구조 처리 가능
5. **부적합 케이스**
- **동적 개수의 반환값**: `Result`는 단일 값만 지원 → 객체/배열로 우회 필요
- **에러 컨텍스트 확장**: 스택 트레이스 등 추가 정보가 필요할 때는 `Error` 확장 클래스 사용 권장
> ✅ **TypeScript 5.9 호환성**: `infer` 및 조건부 타입을 활용한 타입 추론 최적화
> ✅ **타입 안전성**: `any` 대신 명시적 타입 파라미터 사용
> ✅ **Rust 패턴 준수**: `map`, `andThen` 등 메서드 시그니처와 동작 일치채점 근거Result<T,E> discriminated union(_tag)·map/mapErr/andThen/orElse/unwrapOr·AsyncResult까지 정확. 타입 추론 타당.
HyperCLOVAX SEED Think 32BNaver606060606060
```typescript
type Ok<T> = {
_type: 'Ok';
value: T;
};
type Err<E> = {
_type: 'Err';
error: E;
};
type Result<T, E> = Ok<T> | Err<E>;
type AsyncResult<T, E> = Promise<Result<T, E>>;
// Sync 메서드
export const map = <T, U, E>(
result: Result<T, E>,
fn: (value: T) => U
): Result<U, E> => {
return result._type === 'Ok'
? { _type: 'Ok', value: fn(result.value) }
: result;
};
export const mapErr = <T, E, NewE>(
result: Result<T, E>,
fn: (error: E) => NewE
): Result<T, NewE> => {
return result._type === 'Err'
? { _type: 'Err', error: fn(result.error) }
: result;
};
export const andThen = <T, U, E, NewE>(
result: Result<T, E>,
fn: (value: T) => Result<U, NewE>
): Result<U, E | NewE> => {
return result._type === 'Ok' ? fn(result.value) : result;
};
export const orElse = <T, E, NewE>(
result: Result<T, E>,
fn: (error: E) => Result<T, NewE>
): Result<T, E | NewE> => {
return result._type === 'Err' ? fn(result.error) : result;
};
export const unwrapOr = <T, E>(result: Result<T, E>, defaultValue: T): T => {
return result._type === 'Ok' ? result.value : defaultValue;
};
// Async 메서드
export const mapAsync = <T, U, E>(
result: AsyncResult<T, E>,
fn: (value: T) => U
): AsyncResult<U, E> => {
return result.then(r => map(r, fn));
};
export const andThenAsync = <T, U, E, NewE>(
result: AsyncResult<T, E>,
fn: (value: T) => AsyncResult<U, NewE>
): AsyncResult<U, E | NewE> => {
return result.then(r =>
fn(r.value as T).then…채점 근거discriminated union·andThen 에러 union 누적(E|NewE) 시그니처 정확(핵심 discriminator). 그러나 사용예가 메서드 체이닝(.andThen().unwrapOr())인데 구현은 standalone 함수·Ok()/Err() factory 미정의→예제 컴파일 불가. fp-ts/neverthrow 비교 누락. infer/conditional 미활용하며 "활용" overclaim.
Gemma 4 E2BGoogle445043444646
TypeScript 5.9의 강력한 타입 시스템(특히 Conditional Types과 Template Literal Types)을 활용하여 Rust 스타일의 `Result` 패턴과 비동기 체이닝 유틸리티를 구현하겠습니다.
이 구현은 **Discriminated Union**을 사용하여 성공(`Ok`)과 실패(`Err`)를 명확히 구분하며, 타입 안전성을 극대화하는 데 중점을 둡니다.
---
## TypeScript Result Pattern Implementation
### 1. Result 타입 정의 (Discriminated Union)
성공 값 `T`와 에러 값 `E`를 가지는 `Result<T, E>`를 정의합니다.
```typescript
// 1. Result 타입 정의
/**
* 성공적인 결과를 나타냅니다.
* @template T 성공 값 타입
*/
type Ok<T> = { type: 'Ok', value: T };
/**
* 실패한 결과를 나타냅니다.
* @template E 실패 에러 타입
*/
type Err<E> = { type: 'Err', value: E };
/**
* Result<T, E>는 Ok<T> 또는 Err<E> 중 하나입니다.
*/
type Result<T, E> = Ok<T> | Err<E>;
```
### 2. 동기(Sync) 체이닝 유틸리티
`Result<T, E>`에 대해 동작하는 메서드들을 정의합니다.
```typescript
// 2. 동기 체이닝 유틸리티
/**
* Ok<T>의 값을 변환하여 Ok<U>를 반환합니다.
* Err<E>는 그대로 유지됩니다.
* @template T, U
* @param result 현재 Result
* @param f 성공 값 T를 U로 변환하는 함수
* @returns Result<U, E>
*/
export function map<T, U, E>(
result: Result<T, E>,
f: (value: T) => U
): Result<U, E> {
return result.type === 'Ok'
? Ok({ type: 'Ok', value: f(result.value) } as Ok<U>)
: result;
}
/**
* Err<E>의 값을 변환하여 Err<U>를 반환합니다.
* Ok<T>는 그대로 유지됩니다.
* @template T, U, E, U_Err
* @param result 현재 Result
* @param f 실패 값 E를 U_Err로 변환하는 함수
* @returns Result<T, U_Err>
*/
export function mapErr<T, U, E, U_Err>(
result: Result<T, E>,
f: (error: E) => U_Err
): Result<T, U_Err> {
return result.type === 'Err'
? Err({ type: 'Err', value: f(result.value) } as…채점 근거TS Result 컴파일버그, 코드 truncation
Kanana 2 30B-A3B ThinkingKakao457045406556
다음은 TypeScript 5.9에서 Rust 스타일의 `Result<T, E>` 유틸리티를 구현한 코드입니다. 이 구현은 판별 유니온, 메서드 체이닝, 타입 추론, 그리고 `Promise`에 대한 `AsyncResult` 지원을 포함합니다.
```typescript
// 판별 유니온으로 Result 정의
type Result<T, E> = Ok<T> | Err<E>;
interface Ok<T> {
tag: 'ok';
value: T;
}
interface Err<E> {
tag: 'err';
value: E;
}
// 타입 추론을 위한 헬퍼
type AccumulateError<Accum, T, E> = E extends never ? Accum : E;
class Result<T, E> {
private constructor(public readonly tag: 'ok' | 'err', public readonly value: T | E) {}
static ok<T, E>(value: T): Result<T, E> {
return new Result('ok', value);
}
static err<E, T = never>(value: E): Result<T, E> {
return new Result('err', value);
}
// 체이닝 시 에러 타입이 누적됨
andThen<U, F extends (value: T) => Result<U, E1>>(
this: Result<T, E>,
f: F
): Result<U, AccumulateError<E, T, E1>> {
if (this.tag === 'ok') {
return f(this.value);
} else {
return new Result('err', this.value);
}
}
map<U>(f: (value: T) => U): Result<U, E> {
if (this.tag === 'ok') {
return Result.ok(f(this.value));
} else {
return Result.err(this.value);
}
}
mapErr<F>(f: (value: E) => F): Result<T, F> {
if (this.tag === 'err') {
return Result.err(f(this.value));
} else {
return Result.ok(this.value);
}
}
orElse<F extends (value: E) => Result<T, E1>>(
this: Result<T, E>,
f: F
): Result<T, E | E1> {
if (this.tag === 'err') {
return f(this.value);
} else {
return Result.ok(this.value);
}
}
unwrapOr(value: T): T {
if (this.tag === 'ok') {
return this.value;
} else {
return value;
}
}
}
// Promise용 AsyncResult
class AsyncResult<T, E> {
private constructor(private readonly promise: Promise<Result<T, E>>) {}
static async ok<T, E>(value: T): AsyncResult<T, E> {
return new AsyncResult(Promise.resolve(Result.ok(value)));
}
static async err<E, T = never>(value: E): AsyncResult<T, E> {
return new AsyncResult(Promise.resolve(Result.err(value)));
}
map<U>(f: (value: T) => U): AsyncResult<U, E> {
return new AsyncResult(this.promise.then(result => result.map(f)));
}
mapErr<F>(f: (value: E) => F): AsyncResult<T, F> {
return new AsyncResult(this.promise.then(result => result.mapErr(f)));
}
andThen<U, F extends (value: T) => AsyncResult<U, E1>>(
this: AsyncResult<T, E>,
f: F
): AsyncResult<U, E | E1> {
return new AsyncResult(this.promise.then(result => result.andThen(f)));
}
orElse<F extends (value: E) => AsyncResult<T, E1>>(
this: AsyncResult<T, E>,
f: F
): AsyncResult<T, E | E1> {
return new AsyncResult(this.promise.then(result => result.orElse(f)));
}
unwrapOr(value: T): Promise<T> {
return this.promise.then(result => result.unwrapOr(value));
}
}
```
### 사용 예시
**예시 1: 기본 Map**
```typescript
const result = Result.ok(42);
const mapped = result.map(x => x * 2); // Result<number, never>
console.log(mapped.unwrapOr(0)); // 84
```
**예시 2: andThen 체이닝**
```typescript
const result = Result.ok(42);
const chained = result.andThen(x => Result.ok(x * 2)); // Result<number, never>
console.log(chained.unwrapOr(0)); // 84
```
**예시 3: 여러 오류 유형 체이닝**
```typescript
type Error = 'network' | 'auth' | 'invalid';
const result = Result.err('network');
const chained = result
.andThen(x => Result.err('auth'))
.andThen(x => Result.err('invalid')); // Result<never, 'network' | 'auth' | 'invalid'>
console.log(chained.unwrapOr('default')); // 'default'
```
**적합하지 않은 경우: 다양한 오류 유형이 있는 비동기 작업**
```typescript
// 복잡한 오류 처리를 위해 Result 패턴이 적합하지 않음
async function fetchData(): Promise<any> {
// 다양한 오류 유형 (예: network, auth, invalid)이 혼합된 경우
// Result는 하나의 오류 타입만 허용하므로 적합하지 않음
throw new Error('network failure');
}
fetchData()
.catch(error => {
// 직접 처리
});
```
### 주요 특징
1. **판별 유니온**: `Result<T, E>`는 `'ok'` 또는 `'err'` 태그를 가진 두 가지 변형이 있습니다.
2. **메서드 체이닝**: `map`, `mapErr`, `andThen`, `orElse`를 통해 체이닝이 가능합니다.
3. **타입 추론**: `andThen`에서 오류 타입이 누적됩니다 (예: `'network' | 'auth' | 'invalid'`).
4. **AsyncResult**: `Promise<Result<T, E>>`에 대한 동일한 패턴 지원
5. **타입 안전성**: `any`/`unknown`을 피하고, `never` 및 조건부 타입을 사용합니다.
이 구현은 Rust의 Result 패턴을 TypeScript에서 모방하며, 강력한 타입 추론과 체이닝을 제공합니다.채점 근거type alias Result와 class Result를 같은 이름으로 동시 정의(이름충돌로 컴파일 불가), AccumulateError 타입 날조. 골격 의도는 보이나 동작 불가.
LFM2.5 8B-A1BLiquid AI364534363838
Below is a self‑contained TypeScript 5.9 utility module that satisfies every requirement you listed.
```ts
// ──────────────────────────────────────────────────────────────
// Result type – discriminated union (ok / err)
// ──────────────────────────────────────────────────────────────
export type Result<T, E> =
| { ok: T }
| { err: E };
// ──────────────────────────────────────────────────────────────
// 1️⃣ map – transform the *ok* case, leave *err* unchanged
// ──────────────────────────────────────────────────────────────
export function map<T, U>(
fn: (value: T) => Promise<Result<U, E>>,
result: Result<T, E>
): Promise<Result<U, E>>
{
return new Promise((resolve, reject) => {
if (result.ok) {
fn(result.ok).then(resolve);
} else {
reject(result.err);
}
});
}
// ──────────────────────────────────────────────────────────────
// 2️⃣ mapErr – transform the *err* case, return a new Result
// ──────────────────────────────────────────────────────────────
export function mapErr<T, E, E2>(
fn: (error: E) => Promise<Result<T, E2>>,
result: Result<T, E>
): Promise<Result<T, E2>>
{
return new Promise((resolve, reject) => {
if (result.err) {
fn(result.err).then(resolve);
} else {
reject(result.ok);
}
});
}
// ──────────────────────────────────────────────────────────────
// 3️⃣ andThen – chain a function that works on *either* ok or err
// → the resulting error union is automatically the union of …채점 근거Result<T,E> E 스코프밖 컴파일에러+if(result.ok) falsy버그
HyperCLOVAX SEED 1.5BNaver324330333234
TypeScript 5.9에서 Rust 스타일 `Result<T, E>` 타입과 체이닝을 위한 유틸리티를 작성하는 것은 유용한 패턴입니다. 이를 통해 코드의 가독성과 유지보수성을 높일 수 있습니다. 아래는 해당 요구사항을 충족하는 유틸리티 함수들을 정의한 것입니다.
```typescript
// Discriminated unions for Result type
type Result<T, E> =
| { type: 'ok'; value: T }
| { type: 'err'; error: E };
// utility functions for chaining `Result`
// Map error to a new type
function mapErr<T, E, newT>(result: Result<T, E>, err: E, newT: newT): Result(newT, E) {
if (result.type === 'err') {
return { type: 'err', error: err };
}
return { type: 'ok', value: result.value };
}
// andThen with error chaining
function andThen<T, E, newT>(result: Result<T, E>, callback: (err: E) => Result[newT, E])": Result[newT, E] {
if (result.type === 'err') {
return callback(result.error) as Result[newT, E];
}
return callback(result.value) as Result[newT, E];
}
// orElse for handling errors
function orElse<T, E, newT>(result: Result<T, E>, err: E, newT: newT): Result[newT, E] {
if (result.type === 'err') {
return { type: 'err', error: err };
}
return { type: 'ok', value: result.value };
}
// mapErr for chaining with error handling
function mapErrForChain<T, E, newT>(result: Result<T, E>, err: E, newT: newT): Promise st> {
return new Promise(st => {
if (result.type === 'err') {
st resolve(mapErr(result, err, newT) as Promise st));
} else {
st resolve(andThen(result, err => orElse(err, err, newT) as Promise st)) as Promise st;
}
});
}
// orElse for chainin…채점 근거N+1 미식별, 컴파일 불가