JavaScript/함수형 프로그래밍과 JavaScript ES6+

L.flatMap, flatMap

느리지만 꾸준하게 2021. 8. 24. 21:29

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