함수형 유틸리티 라이브러리 사용 (fp-ts)
fp-ts는 타입스크립트를 위한 강력한 함수형 프로그래밍 라이브러리입니다.
이 라이브러리는 타입 안전성과 함께 함수형 프로그래밍의 핵심 개념들을 구현할 수 있게 해줍니다.
fp-ts 개요와 이점
fp-ts는 다음과 같은 이점을 제공합니다.
- 타입 안전한 함수형 프로그래밍 구조
- 일관된 API를 통한 다양한 함수형 개념 구현
- 코드의 가독성과 재사용성 향상
- 부수 효과의 명시적 관리
주요 데이터 타입
- Option : null 또는 undefined를 안전하게 다루기 위한 타입
import { Option, some, none, pipe } from 'fp-ts/Option'
import { pipe } from 'fp-ts/function'
function divide(a: number, b: number): Option<number> {
return b === 0 ? none : some(a / b)
}
const result = pipe(
divide(10, 2),
Option.map(x => x * 2),
Option.getOrElse(() => 0)
)
console.log(result) // 10
- Either : 에러 처리를 위한 타입
import { Either, right, left } from 'fp-ts/Either'
import { pipe } from 'fp-ts/function'
function sqrt(x: number): Either<string, number> {
return x < 0 ? left('Cannot calculate square root of negative number') : right(Math.sqrt(x))
}
const result = pipe(
sqrt(-1),
Either.map(x => x * 2),
Either.getOrElse(() => 0)
)
console.log(result) // 0
- Task : 비동기 연산을 위한 타입
import { Task } from 'fp-ts/Task'
import { pipe } from 'fp-ts/function'
const delay = (ms: number): Task<void> => () => new Promise(resolve => setTimeout(resolve, ms))
const program = pipe(
delay(1000),
Task.chain(() => Task.of('Hello, world!'))
)
program().then(console.log) // 1초 후 "Hello, world!" 출력
함수 합성, 에러 처리, 비동기 프로그래밍
- 함수 합성
import { flow } from 'fp-ts/function'
const double = (n: number) => n * 2
const addOne = (n: number) => n + 1
const doubleThenAddOne = flow(double, addOne)
console.log(doubleThenAddOne(3)) // 7
- 에러 처리
import { Either, right, left } from 'fp-ts/Either'
import { pipe } from 'fp-ts/function'
const safeDivide = (a: number, b: number): Either<string, number> =>
b === 0 ? left('Division by zero') : right(a / b)
const result = pipe(
safeDivide(10, 2),
Either.chain(x => safeDivide(x, 2)),
Either.map(x => x * 2),
Either.getOrElse(() => 0)
)
console.log(result) // 5
- 비동기 프로그래밍
import { TaskEither, tryCatch } from 'fp-ts/TaskEither'
import { pipe } from 'fp-ts/function'
const fetchUser = (id: number): TaskEither<Error, User> =>
tryCatch(
() => fetch(`https://api.example.com/users/${id}`).then(res => res.json()),
reason => new Error(String(reason))
)
const program = pipe(
fetchUser(1),
TaskEither.map(user => user.name)
)
program().then(console.log)
타입 클래스와 다형적 코드
fp-ts는 Functor, Applicative, Monad 등의 타입 클래스를 제공합니다.
import { Option, some, none } from 'fp-ts/Option'
import { Either, right, left } from 'fp-ts/Either'
import { Functor2 } from 'fp-ts/Functor'
const double = <F extends Functor2<any>>(F: F) => (fa: F['_1']) =>
F.map(fa, (a: number) => a * 2)
const doubleOption = double(Option)
const doubleEither = double(Either)
console.log(doubleOption(some(5))) // some(10)
console.log(doubleEither(right(5))) // right(10)
고급 모나드 사용
- Reader 모나드 : 의존성 주입에 유용
import { Reader } from 'fp-ts/Reader'
import { pipe } from 'fp-ts/function'
interface Environment {
config: { apiUrl: string }
logger: { log: (message: string) => void }
}
const getApiUrl: Reader<Environment, string> = ({ config }) => config.apiUrl
const logMessage = (message: string): Reader<Environment, void> =>
({ logger }) => logger.log(message)
const program = pipe(
getApiUrl,
Reader.chain(url => logMessage(`API URL is ${url}`))
)
const env: Environment = {
config: { apiUrl: 'https://api.example.com' },
logger: { log: console.log }
}
program(env)
- State 모나드 : 상태 관리에 유용
import { State } from 'fp-ts/State'
import { pipe } from 'fp-ts/function'
const increment: State<number, void> = state => [undefined, state + 1]
const getState: State<number, number> = state => [state, state]
const program = pipe(
increment,
State.chain(() => increment),
State.chain(() => getState)
)
console.log(program(0)) // [2, 2]
타입 안전성 극대화
fp-ts와 타입스크립트의 타입 시스템을 결합하여 타입 안전성을 높일 수 있습니다.
import { Option, some, none } from 'fp-ts/Option'
import { pipe } from 'fp-ts/function'
type User = { name: string; age: number }
const getUser = (id: number): Option<User> =>
id === 1 ? some({ name: "John", age: 30 }) : none
const getAge = (user: User): Option<number> =>
user.age >= 18 ? some(user.age) : none
const program = pipe(
getUser(1),
Option.chain(getAge),
Option.map(age => `User is ${age} years old`),
Option.getOrElse(() => "User not found or not an adult")
)
console.log(program) // "User is 30 years old"
도입 전략과 리팩토링
- 부분적 도입 : 특정 모듈이나 기능에 fp-ts를 적용
- 점진적 확장 : 성공적인 적용 후 다른 부분으로 확장
- 기존 코드와의 통합 : fp-ts의 데이터 타입과 기존 타입 간 변환 함수 작성
- 팀 교육 : fp-ts와 함수형 프로그래밍 개념에 대한 워크샵 진행
성능 고려사항과 최적화
- 불필요한 래핑 피하기 : 과도한 모나드 사용은 성능 저하 가능
- 메모이제이션 활용 : 순수 함수에 대해 메모이제이션 적용
- 효율적인 데이터 구조 선택 : 상황에 맞는 적절한 데이터 구조 사용
Best Practices와 주의사항
- 일관성 유지 : 프로젝트 전체에서 fp-ts 사용 패턴 통일
- 문서화 : 복잡한 함수형 구조에 대한 설명 제공
- 테스트 작성 : 함수형 코드에 대한 단위 테스트 강화
- 과도한 추상화 주의 : 필요 이상의 복잡한 구조 지양
- 성능 모니터링 : fp-ts 사용에 따른 성능 영향 주기적 검토
대안 라이브러리 비교
- Ramda : 자바스크립트 중심, 타입 추론이 fp-ts에 비해 제한적
- Lodash/FP : 널리 사용되지만 타입 안전성이 fp-ts에 비해 떨어짐
- Purify-ts : fp-ts와 유사하지만 더 작은 규모의 라이브러리
fp-ts는 타입스크립트에서 함수형 프로그래밍을 구현하는 강력한 도구입니다.
이 라이브러리를 효과적으로 활용하면 더 안전하고 유지보수가 쉬운 코드를 작성할 수 있습니다.