함수형 프로그래밍과 JavaScript ES6+ 강의를 듣고 정리한 내용입니다.
✅ L.flatten
[[1, 2], 3, 4, [5, 6], [7, 8, 9]]와 같은 값이 들어왔을 때 결과값으로 [1, 2, 3, 4, 5, 6, 7, 8, 9] 와 같이 값을 펼친 결과를 리턴하는 함수이다.
다시 말해 위와 같은 값이 들어왔을 때 L.flatten을 사용하여 [...[1, 2], 3, 4, ...[5, 6], ...[7, 8, 9]] 처럼 동작하려 한다.
전개 연산자를 사용한 것과 같은 작업을 하기 위해서는 먼저 인자로 주어진 이터러블을 순회한다.
그러다 이터러블인 요소를 만나면 이들을 다 이터러블이 아닌 값이 될 때까지 값을 펼친다.
const isIterable = (a) => a && a[Symbol.iterator];
L.flatten = function* (iter) {
for (const a of iter) {
if (isIterable(a)) for (const b of a) yield b;
else yield a;
}
};
L.flatten은 지연성을 가지기 위해 제너레이터 함수로 만들어진다.
인자로 주어진 이터러블(iter)의 안을 순회하며 각 요소가 이터러블인지 아닌지를 isIterable 함수로 평가한다.
isIterable은 어떠한 값 a를 받아 a && a[Symbol.iterator]를 리턴하는 단순 동작을 한다.
a[Symbol.iterator]가 존재한다는 것은 a가 이터러블이라는 뜻이다.
다시 말하자면 a의 요소를 순회하면서 yield 키워드를 적용할 수 있다.
따라서 a[Symbol.iterator]가 존재하면 for of문으로 a의 요소들을 순회하며 yield 키워드를 적용해 값을 만든다.
var it = L.flatten([[1, 2], 3, 4, [5, 6], [7, 8, 9]]);
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
// output
// {value: 1, done: false}
// {value: 2, done: false}
// {value: 3, done: false}
// {value: 4, done: false}
L.flatten은 제너레이터이기 때문에 이터레이터를 이용해 next()로 순회가 가능하다.
즉, 원하는 만큼 평가해서 원하는 만큼 값을 얻을 수 있다.
✅ L.flatten + take로 flatten 만들기
앞서 map과 filter를 각각 L.map과 L.filter로 만들었던 것과 같은 원리로 L.flatten과 take로 flatten을 만들 수 있다.
const flatten = pipe(L.flatten, take(Infinity));
console.log(flatten([[1, 2], 3, 4, [5, 6], [7, 8, 9]]));
// output
// [1, 2, 3, 4, 5, 6, 7, 8, 9]
✅ yield *iterable
yield *iterable은 for(const val of iterable) yield val; 와 같은 동작을 한다.
따라서 yield *iterable을 사용해 위의 L.flatten을 구현한 것을 아래와 같이 바꿀 수 있다.
L.flatten = function* (iter) {
for (const a of iter) {
if (isIterable(a)) yield *a;
else yield a;
}
};
✅ L.deepFlat
[ 1, [ 2, [ 3, 4 ], [ [ 5 ] ] ] ] 와 같이 깊은 iterable이 있다고 할 때
기존의 L.flatten의 결과값처럼 [1, 2, 3, 4, 5]를 얻어내고 싶으면 재귀함수를 사용하면 된다.
L.deepFlat = function* f(iter) {
for (const a of iter) {
if (isIterable(a)) {
yield *f(a);
}
else yield a;
}
}
console.log(take(Infinity, L.deepFlat([1, [2, [3, 4], [[5]]]])));
// output
// [1, 2, 3, 4, 5]
L.deepFlat을 구현할 때 제너레이터 함수의 이름을 f라고 주었다.
인자로 주어진 iter의 요소들을 순회하다 a가 이터러블이면 재귀적으로 다시 함수 f를 호출한다.
이 함수가 동작하는 것을 자세히 살펴보기 위해 다음과 같이 break point를 두고 동작을 살펴보았다.
iter는 [1, [2, [3, 4], [[5]]]]가 주어졌다.
이때 함수의 동작 과정은 다음과 같다.
- a: 1 => 이터러블이 아니므로 yield a로 바로 값으로 빠져나감
- a: (3) [2, Array(2), Array(1)] => 재귀 실행
- a: 2 => 이터러블이 아니므로 yield a로 바로 값으로 빠져나감
- a: (2) [3, 4] => 재귀 실행
- a: 3 => 이터러블이 아니므로 yield a로 바로 값으로 빠져나감
- a: 4 => 이터러블이 아니므로 yield a로 바로 값으로 빠져나감
- a: [[5]] => 재귀 실행
- a: [5] => 재귀 실행
- a: 5 => 이터러블이 아니므로 yield a로 바로 값으로 빠져나감
이터러블인 요소를 만나면 재귀적 호출을 통해 그 안으로 파고들어간다.
그리고 더이상 파고들어갈 곳이 없을 때 함수를 종료시켜가며 다시 위로 올라오는 과정을 거친다.
이러한 과정을 통해 [ 1, 2, 3, 4, 5 ]라는 값을 얻을 수 있다.
'JavaScript' 카테고리의 다른 글
[FP&ES6+] callback, Promise를 이용한 비동기 동시성 프로그래밍 (0) | 2022.01.02 |
---|---|
[FP&ES6+] L.flatMap, flatMap (0) | 2021.12.28 |
[FP&ES6+] L.map, L.filter로 map, filter 만들기 (0) | 2021.12.27 |
[FP&ES6+] 결과를 만드는 함수 - take (0) | 2021.12.27 |
[FP&ES6+] 결과를 만드는 함수 - reduce (0) | 2021.12.27 |
댓글