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
'JavaScript > 함수형 프로그래밍과 JavaScript ES6+' 카테고리의 다른 글
이터러블 중심 프로그래밍 실무적인 코드 (0) | 2021.08.25 |
---|---|
2차원 배열 다루기 (0) | 2021.08.24 |
L.flatten, flatten (0) | 2021.08.24 |
L.map L.filter로 map과 filter 만들기 (0) | 2021.08.24 |
take, find (0) | 2021.08.24 |