-
[Functional] join, find, flattenprograming/Language 2019. 8. 12. 12:14
안녕하세요, Einere입니다.
(ADblock을 꺼주시면 감사하겠습니다.)
2019/06/25 - [programing/JavaScript] - [Functional] 평가와 일급, 고차함수
2019/07/15 - [programing/JavaScript] - [Functional] 순회와 이터러블
2019/07/15 - [programing/JavaScript] - [Functional] 제네레이터와 이터러블
2019/07/15 - [programing/JavaScript] - [Functional] map, filter, reduce
2019/07/18 - [programing/JavaScript] - [Functional] 코드를 값으로 다루어 표현력 높이기
2019/07/22 - [programing/JavaScript] - [Functional] range,take, 느긋한 L.range, L.take
2019/07/22 - [programing/JavaScript] - [Functional] 지연 평가와 L.map, L.filter
이번 포스트에서는 객체를 받아서 query string으로 만들어주는 함수를 만들어보도록 하겠습니다. 이를 위해서, join함수를 정의합니다. 그리고, Array.prototype.find를 이터러블하게 구현한 find함수도 만들어보도록 하겠습니다.
toQueryStr
const toQueryStr = obj => L.go( obj, Object.entries, L.map(([k, v])=> `${k}=${v}`), L.reduce((a, v) => `${a}&${v}`) ); console.log(toQueryStr({a:1, b:2, c:3})); // 결과 // a=1&b=2&c=3
const toQueryStr = L.pipe( Object.entries, L.map(([k, v])=> `${k}=${v}`), L.reduce((a, v) => `${a}&${v}`) ); console.log(toQueryStr({a:1,b:2,c:3})); // 출력은 위 코드와 동일
위 로직은 Array.prototype.join과 유사한 기능을 합니다.
그러나 이 함수는 Array에 대해서만 동작하기 때문에, 범용성이 좋지 않습니다.
따라서, 이터러블에 대해 동작하는 개선된 join함수를 작성하도록 하겠습니다.
join
const join = L.curry((separator = ",", iterator) => L.reduce((a, v) => `${a}${separator}${v}`, iterator)); const toQueryStr = L.pipe( Object.entries, L.map(([k, v]) => `${k}=${v}`), L.join('&') ); console.log(toQueryStr({ a: 1, b: 2, c: 3 })); // 출력은 위와 동일
join함수는 위와 같이 구현할 수 있습니다.
const toQueryStr = L.pipe( Object.entries, L.map(([k, v]) => `${k}=${v}`), join() // 디폴트 파라미터를 설정하기 위해, 인자를 주지 않고 실행한다 );
만약 디폴트 세퍼레이터를 사용하고 싶다면 위와 같이 인자 없이 함수를 실행하면 됩니다.
find
const users = [ {age: 35}, {age: 40}, {age: 32}, {age: 28} ]; const find = (f, iter) => L.go( iter, L.filter(f), L.take(1), ([user]) => user // 반환값이 배열이기 때문에, 요소를 뽑아줍니다 ); console.log(find(user => user.age > 30, users)); // 출력 : { age: 35 }
find는 filter를 이용해서 iterator내에서 조건을 만족하는 첫번째 요소만 추출하는 함수입니다.
flatten
const isIterable = val => val && val[Symbol.iterator]; const flatten = function* (iter) { for (const outer of iter) { if (isIterable(outer)) { for (const inner of outer) yield inner; } else { yield outer; } } }; const arr = [[1, 2], 3, 4, [5, 6, 7]]; console.log(L.go(arr, flatten, L.take(5))); // 출력 : [1, 2, 3, 4, 5] console.log(L.take(3, flatten(arr)); // 출력 : [1, 2, 3]
flatten함수는 깊이가 2이상인 이터레이터(특히 배열)를 깊이가 1인 이터레이터로 평탄화해주는 함수입니다. 전개 연산자와 유사한 역할을 합니다.
pipe를 이용한다면 다음과 같이 리팩토링할 수 있습니다.
const flatten = L.pipe(function* (iter) { for (const outer of iter) { if (L.isIterable(outer)) { for (const inner of outer) yield inner; } else { yield outer; } } }, take(Infinity));
근데 사실 그냥 go혹은 take를 쓰는게 더 나을 것 같네요. 몇 개의 요소를 취할지 알 수 없으니까요.
const flatten = function* (iter) { for (const outer of iter) { if (L.isIterable(outer)) yield *outer; else yield outer; } };
또한 yield *를 이용하여 위와 같이 리팩토링 할 수 있습니다.
deepFlatten
const deepFlatten = function* flatten(iter) { for(const outer of iter) { if(L.isIterable(outer)) yield *flatten(outer); else yield outer; } }; const arr = [[1, 2], 3, 4, [5, [6, [7, [8, [9, 10]]]]]]; console.log(L.take(Infinity, deepFlatten(arr))); // 출력 : [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
재귀함수를 이용하여 모든 깊이에 대해 깊이가 1인 이터레이터(특히 배열)로 변환하는 deepFlatten함수를 구현할 수 있습니다.
참고
마이클 포거스, 『함수형 자바스크립트 : 새롭고 올바른 자바스크립트 프로그래밍 기법』, 한빛미디어(2014).
유인동, 인프런 - 함수형 프로그래밍과 JavaScript ES6+
'programing > Language' 카테고리의 다른 글
[Functional] 동시성을 가진 C.reduce, C.take, C.map, C.filter (0) 2019.08.21 [Functional] promise와 monad, kleisli composition (0) 2019.08.15 [JS] Date 객체 사용시 주의할 점 (0) 2019.08.04 [JS] Scope, Execution context, Closure (0) 2019.08.03 [JS] SVG에 대해서 (0) 2019.08.02 댓글