프로그래밍 공부/javascript & typescript
[Functional Programming] 1장: 기본 배열 함수 구현하기
99mini
2025. 4. 19. 08:00
반응형
SMALL
0. 프로젝트 구조 및 라이브러리
├── jest.config.ts
├── package.json
├── tsconfig.json
├── /src
│ ├── index.ts
│ └── index.spec.ts
사용한 라이브러리 (devDependency)
"@types/jest": "^29.5.14",
"jest": "^29.7.0",
"ts-jest": "^29.3.2",
"ts-node": "^10.9.2",
"typescript": "^5.8.3"
1. Fx
클래스 도입
export class Fx<T> implements Iterable<T> {
constructor(private iterable: Iterable<T>) {}
[Symbol.iterator]() {
return this.iterable[Symbol.iterator]();
}
static of<T>(iter: Iterable<T>): Fx<T> {
return new Fx(iter);
}
// ...
}
static of(...)
:Fx
객체를 생성하는 팩토리 메서드입니다.Symbol.iterator()
:Fx
자체도 이터러블로 사용할 수 있습니다.
체이닝 가능한 API 설계
Fx
의 모든 method는 Fx
인스턴스를 반환하며 불편성을 지키며 메서드 체이닝이 가능하게 설계합니다.
Fx.of(data)
.filter(...)
.map(...)
.take(...)
.toArray()
.map(fn) → Fx<U>
.filter(fn) → Fx<T>
.take(n) → Fx<T>
2. map
map<U>(fn: (item: T) => U): Fx<U> {
const self = this;
function* generator() {
for (const item of self.iterable) {
yield fn(item);
}
}
return new Fx(generator());
}
map
은 각 항목에fn
함수를 적용하여 새 값을 만들어냅니다.function*
으로 구현했기 때문에 지연 평가(lazy evaluation)가 작동합니다. 실제로.toArray()
등으로 소비되기 전까지 연산이 수행되지 않습니다.- 원본 데이터는 그대로 유지되고 변환된 새로운
Fx
가 생성됩니다.
3. filter
filter(fn: (item: T) => boolean): Fx<T> {
const self = this;
function* generator() {
for (const item of self.iterable) {
if (fn(item)) yield item;
}
}
return new Fx(generator());
}
filter
는 각 항목에 조건을 적용해true
인 경우만yield
합니다.Fx
는 배열이 아니기 때문에 메모리 전체에 값을 저장하지 않으며 필요한 값만 즉시 계산하여 반환합니다.
4. take
take(n: number): Fx<T> {
const self = this;
function* generator() {
let i = 0;
for (const item of self.iterable) {
if (i++ >= n) break;
yield item;
}
}
return new Fx(generator());
}
take(n)
은 최대 n개까지만 값을 생성합니다.- 배열의
slice(0, n)
과는 달리 앞에서부터 순회하며 조건 만족 시 즉시 종료합니다. - 무한한 이터러블에도 사용할 수 있는 점에서 유용하게 사용될 수 있습니다.
예시
Fx.of(range()) // 무한 range generator
.map(x => x * x)
.filter(x => x % 2 === 0)
.take(10)
.toArray();
5. toArray
toArray(): T[] {
return [...this.iterable];
}
toArray()
는 지연된 이터러블을 한 번에 평가하여 실제 배열로 변환합니다.- 이 함수가 호출되기 전까지는
map
,filter
,take
등은 평가되지 않고 순수하게 설정된 연산만 보관됩니다.
6. 전체코드
// index.ts
export class Fx<T> implements Iterable<T> {
private iterable: Iterable<T>;
constructor(iterable: Iterable<T>) {
this.iterable = iterable;
}
static of<T>(iterable: Iterable<T>): Fx<T> {
return new Fx(iterable);
}
map<U>(fn: (item: T) => U): Fx<U> {
const self = this;
function* generator() {
for (const item of self.iterable) {
yield fn(item);
}
}
return new Fx(generator());
}
filter(fn: (item: T) => boolean): Fx<T> {
const self = this;
function* generator() {
for (const item of self.iterable) {
if (fn(item)) yield item;
}
}
return new Fx(generator());
}
take(n: number): Fx<T> {
const self = this;
function* generator() {
let i = 0;
for (const item of self.iterable) {
if (i++ >= n) break;
yield item;
}
}
return new Fx(generator());
}
toArray(): T[] {
return [...this.iterable];
}
[Symbol.iterator](): Iterator<T> {
return this.iterable[Symbol.iterator]();
}
}
반응형
LIST