고차 함수와 커링
고차 함수와 커링은 함수형 프로그래밍의 핵심 개념으로, 코드의 재사용성과 조합성을 높이는 데 중요한 역할을 합니다.
타입스크립트에서 이러한 개념을 적용하면 타입 안정성과 함께 더욱 유연한 코드를 작성할 수 있습니다.
고차 함수의 정의와 구현
고차 함수는 함수를 인자로 받거나 함수를 반환하는 함수입니다.
타입스크립트에서의 고차 함수 예시
type Predicate<T> = (value: T) => boolean;
type Transformer<T, U> = (value: T) => U;
function filter<T>(array: T[], predicate: Predicate<T>): T[] {
return array.filter(predicate);
}
function map<T, U>(array: T[], transformer: Transformer<T, U>): U[] {
return array.map(transformer);
}
// 사용 예
const numbers = [1, 2, 3, 4, 5];
const evenNumbers = filter(numbers, n => n % 2 === 0);
const doubledNumbers = map(numbers, n => n * 2);
커링(Currying)의 개념과 구현
커링은 여러 인자를 받는 함수를 단일 인자를 받는 함수들의 체인으로 변환하는 기법입니다.
타입스크립트에서의 커링 구현
function curry<T1, T2, R>(fn: (a: T1, b: T2) => R): (a: T1) => (b: T2) => R {
return (a: T1) => (b: T2) => fn(a, b);
}
const add = (a: number, b: number) => a + b;
const curriedAdd = curry(add);
console.log(curriedAdd(2)(3)); // 5
제네릭을 활용한 유연한 고차 함수
제네릭을 사용하여 더 유연하고 재사용 가능한 고차 함수를 작성할 수 있습니다.
function compose<T, U, V>(f: (x: U) => V, g: (x: T) => U): (x: T) => V {
return (x: T) => f(g(x));
}
const double = (x: number) => x * 2;
const increment = (x: number) => x + 1;
const doubleAndIncrement = compose(increment, double);
console.log(doubleAndIncrement(3)); // 7
부분 적용과 커링의 차이
부분 적용(Partial Application)은 함수의 일부 인자를 미리 채우는 기법입니다. 커링과는 다르게, 남은 인자를 한 번에 제공할 수 있습니다.
function partial<T1, T2, R>(fn: (a: T1, b: T2) => R, a: T1): (b: T2) => R {
return (b: T2) => fn(a, b);
}
const greet = (greeting: string, name: string) => `${greeting}, ${name}!`;
const greetHello = partial(greet, "Hello");
console.log(greetHello("Alice")); // "Hello, Alice!"
함수 합성(Function Composition)
함수 합성은 여러 함수를 조합하여 새로운 함수를 만드는 기법입니다.
function compose<T>(...fns: Array<(arg: T) => T>): (arg: T) => T {
return fns.reduce((prevFn, nextFn) =>
(value: T) => nextFn(prevFn(value))
);
}
const addOne = (x: number) => x + 1;
const double = (x: number) => x * 2;
const square = (x: number) => x * x;
const addOneAndDoubleAndSquare = compose(addOne, double, square);
console.log(addOneAndDoubleAndSquare(3)); // 64
타입 추론을 활용한 고차 함수와 커링
타입스크립트의 타입 추론을 활용하여 고차 함수와 커링된 함수의 타입을 정확하게 추론할 수 있습니다.
function curry<T1, T2, R>(fn: (a: T1, b: T2) => R) {
return (a: T1) => (b: T2) => fn(a, b);
}
const curriedAdd = curry((a: number, b: number) => a + b);
// curriedAdd의 타입은 (a: number) => (b: number) => number로 추론됩니다.
const add5 = curriedAdd(5);
// add5의 타입은 (b: number) => number로 추론됩니다.
console.log(add5(3)); // 8
함수형 에러 처리 패턴
Either 모나드를 사용한 함수형 에러 처리 패턴
type Either<L, R> = Left<L> | Right<R>;
class Left<L> {
constructor(private value: L) {}
isLeft(): this is Left<L> { return true; }
isRight(): this is Right<never> { return false; }
fold<T>(leftFn: (left: L) => T, rightFn: (right: never) => T): T {
return leftFn(this.value);
}
}
class Right<R> {
constructor(private value: R) {}
isLeft(): this is Left<never> { return false; }
isRight(): this is Right<R> { return true; }
fold<T>(leftFn: (left: never) => T, rightFn: (right: R) => T): T {
return rightFn(this.value);
}
}
function tryCatch<T>(fn: () => T): Either<Error, T> {
try {
return new Right(fn());
} catch (e) {
return new Left(e instanceof Error ? e : new Error(String(e)));
}
}
// 사용 예
const result = tryCatch(() => JSON.parse('{"name": "John"}'))
.fold(
error => console.error("Error:", error.message),
data => console.log("Data:", data)
);
Best Practices와 주의사항
- 타입 안정성 유지 : 제네릭과 함수 시그니처를 활용하여 타입 안정성을 확보하세요.
- 순수 함수 사용 : 고차 함수와 커링은 가능한 한 순수 함수로 구현하세요.
- 가독성 고려 : 과도한 중첩은 코드의 가독성을 해칠 수 있으므로 주의하세요.
- 성능 고려 : 커링과 함수 합성이 성능에 미치는 영향을 고려하세요.
- 부분 적용 활용 : 커링 대신 부분 적용이 더 적합한 상황인지 검토하세요.
- 에러 처리 : Either 모나드 등을 활용하여 일관된 에러 처리 패턴을 구축하세요.
- 문서화 : 복잡한 고차 함수나 커링된 함수는 사용법과 목적을 명확히 문서화하세요.
- 테스트 작성 : 고차 함수와 커링된 함수에 대한 단위 테스트를 작성하세요.
- 점진적 도입 : 팀의 역량을 고려하여 점진적으로 도입하세요.
- 라이브러리 활용 : 필요한 경우 lodash-fp 같은 검증된 라이브러리를 활용하세요.
고차 함수와 커링은 타입스크립트에서 강력한 추상화와 코드 재사용 도구입니다.
이들을 적절히 활용하면 더 선언적이고 조합 가능한 코드를 작성할 수 있습니다.
타입스크립트와 결합하면 타입 안정성을 유지하면서도 유연한 함수형 프로그래밍 패턴을 구현할 수 있습니다.
그러나 이러한 기법들은 올바르게 사용되지 않으면 오히려 코드를 복잡하게 만들 수 있습니다.