L.flatMap, flatMap
flatMap은 map과 flat을 동시에 하는 함수라고 생각하자.
기본적으로 최신 자바스크립트 스펙에 flatmap이 들어가는데 flatMap이 있는 이유는 자바스크립트가 기본적으로 지연적으로 동작하지 않기 때문에 그렇다.
지연적으로 동작하는 flatten이 있다면 지연적으로 동작하는 L.map을 한 후에 flatten을 할 경우에 마치 flatMap 한 번에 두 가지를 동시에 하는 flatMap과 동일하게 동작을 하게 된다.
flatMap을 먼저 사용해보자. 배열안에 있는 값들을 그대로 전달을 하고
<script>
log([[1, 2], [3, 4], [5, 6, 7]].flatMap(a => a));
</script>
//(7) [1, 2, 3, 4, 5, 6, 7]
그리고 배열이 아닌값이 있더라고 a => a를 줬기 때문에 펼쳐진 결과가 나온다.
<script>
log([[1, 2], [3, 4], [5, 6, 7], 8, 9, [10]].flatMap(a => a));
</script>
// (10) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
flatMap의 용도는 a => a 함수를 전달해서 안쪽에 있는 값들을 [1, 2] 값이나 [3, 4]값들을 가지고 변화를 줘서 사용할 수 있다는 점이 특징이 될 것이다.
예를 들어 a라는 값을 map을 하면서 제곱을 해줘서 안쪽에 있는 값을 하나하나 제곱해주고 한 번에 flat까지 하는 일을 처리 할 수 있다.
<script>
log([[1, 2], [3, 4], [5, 6, 7]].flatMap(a => a));
log([[1, 2], [3, 4], [5, 6, 7]].flatMap(a => a.map(a => a * a)));
</script>
// (7) [1, 2, 3, 4, 5, 6, 7]
// 1prac.html:79 (7) [1, 4, 9, 16, 25, 36, 49]
flatMap하고 동일하게 동작하는 코드를 작성해보면 map을 한 후에 flatten을 한 것과 동일하게 동작하는 것이 flattenMap이다.
log([[1, 2], [3, 4], [5, 6, 7]].map(a => a.map(a => a * a)));
log(flatten([[1, 2], [3, 4], [5, 6, 7]].map(a => a.map(a => a * a))));
//(7) [1, 4, 9, 16, 25, 36, 49]
flatMap이 있는 이유는 map과 flatten이 결국에는 좀 비효율적으로 동작하기 때문에 그렇다.
앞에서 map을 하면서 첫번째 map을 할 때 새로운 배열이 하나가 만들어 진다.
즉 안에 있는 모든 값을 순회하면서 새로운 배열을 한 번 만들게 되고 모든 배열을 만든 후에 다시 한번 전체를 순회 하면서 배열을 다시 담기 때문에 약간의 비효율이 발생한다.
log([[1, 2], [3, 4], [5, 6, 7]].map(a => a.map(a => a * a)));
log(flatten([[1, 2], [3, 4], [5, 6, 7]].map(a => a.map(a => a * a))));
//(7) [1, 4, 9, 16, 25, 36, 49]
그래서 한 번에 해주는 코드를 통해서 좀 더 효율적으로 동작하게 끔 하는 것이다.
log([[1, 2], [3, 4], [5, 6, 7]].flatMap(a => a.map(a => a * a)));
// //(7) [1, 4, 9, 16, 25, 36, 49]
좀 더 지연적으로 동작하는 효율성이 있는 flatMap 즉, 이터러블을 모두 다룰 수 있는 flatMap을 만들어 보면
L.flatMap이란 함수는 이미 flatten이 구현되어 있기 때문에 굉장히 단순하게 구현할 수가 있다.
L.map을 한 번 하고 L.flatten을 한 것이 L.flatMap이므로
L.map에서도 지연적으로 안쪽에 있는 함수를 적용한 값을 지연적으로 이터레이터로 만들어서 리턴을 해주고 flatten에서는 flatten이 하는 일만 이터러블 해서 해주기 때문에 아래와 같이 구현할 수 있다.
L.flatMap = curry(pipe(L.map, L.flatten));
flatMap은 지연적으로 동작하고 이터레이터를 리턴하는 함수이기 때문에 아래와 같이 펼치면서 만들 수가 있다.
L.flatMap = curry(pipe(L.map, L.flatten));
const at = L.flatMap(map(a => a * a), [[1, 2], [3, 4], [5, 6, 7]]);
log(at.next());
log(at.next());
log(at.next());
log(at.next());
spread operator를 이용해서도 아래와 같이 만들 수가 있다.
const at = L.flatMap(map(a => a * a), [[1, 2], [3, 4], [5, 6, 7]]);
log([...at]);
// (7) [1, 4, 9, 16, 25, 36, 49]
const at = L.flatMap(a => a * a, [[1, 2], [3, 4], [5, 6, 7]];
log([...at]);
// (7) [1, 4, 9, 16, 25, 36, 49]
L.flatMap는 평가를 끝까지 한 번더 미룬 L.flatten으로 끝내고
const flatMap은 똑같이 L.map을 하지만 flatten에서 평가를 모두 완료한 flatMap으로 만드는 것이다.
L.flatMap = curry(pipe(L.map, L.flatten));
const flatMap = curry(pipe(L.map, flatten));
그래서 L.map과 flatten을 이용한 flatMap을 사용할 경우에는 바로 평가까지 완료된 flatMap이 된다. 2차원 배열 안에 있는 값을 조회하는 식으로만 사용하는 것은 아니고 응용도 할 수 있다.
const flatMap = curry(pipe(L.map, flatten));
log(flatMap(a => a, [[1, 2], [3, 4], [5, 6, 7]]));
// (7) [1, 2, 3, 4, 5, 6, 7]
flatMap을 하면서 3개의 배열 값이 들어있고 L.range를 하겠다하고 확인을 해보면 1을 가지고 range를 해서 0 하나
2를 가지고 range 0 1 만들어지고 3을 가지고 range 0 1 2 3개가 만들어진다.
log(flatMap(L.range, [1, 2, 3]));
// (6) [0, 0, 1, 0, 1, 2]
flatMap 말고 그냥 map으로 했다면 아래와 같다. 즉 flatMap을 통해서 펼칠 수가 있다.
log(map(range, [1, 2, 3]));
그리고 range를 하기전에 map을 한번 더 적용해서 a + b를 해주고 하면 아래와 같이 만들 수 있다.
log(flatMap(range, map(a => a + 1, [1, 2, 3])));
// (9) [0, 1, 0, 1, 2, 0, 1, 2, 3]
spread operator를 이용해서 L.flatMap와 L.range로도 아래와 같이 만들 수 있다.
log(...L.flatMap(L.range, map(a => a + 1, [1, 2, 3])));
// 0 1 0 1 2 0 1 2 3
아래는 즉시 평가되는 flatMap
log(flatMap(L.range, map(a => a + 1, [1, 2, 3])));
// (9) [0, 1, 0, 1, 2, 0, 1, 2, 3]
그리고 이터레이터로 만든 후에 next를 하면서 필요할 때까지 순회를 할 수도 있다.
const iit = L.flatMap(L.range, map(a => a + 1, [1, 2, 3]));
log(iit.next());
log(iit.next());
log(iit.next());
log(iit.next());
// {value: 0, done: false}
// 1prac.html:101 {value: 1, done: false}
// 1prac.html:102 {value: 0, done: false}
// 1prac.html:103 {value: 1, done: false}
take해서 앞에서 3개를 꺼내서 볼 수도 있다.
log(take(3, L.flatMap(L.range, map(a => a + 1, [1, 2, 3]))));
// (3) [0, 1, 0]
지금까지 flatMap과 flatten을 만들어 보았다.
<출처 : 유인동 함수형 프로그래밍과 JavaScript ES6+>
https://www.inflearn.com/course/functional-es6/dashboard
함수형 프로그래밍과 JavaScript ES6+ - 인프런 | 강의
ES6+와 함수형 프로그래밍을 배울 수 있는 강의입니다. 이 강좌에서는 ES6+의 이터러블/이터레이터/제너레이터 프로토콜을 상세히 다루고 응용합니다. 이터러블을 기반으로한 함수형 프로그래밍,
www.inflearn.com