함수형 프로그래밍과 javascript es6 구현하기 - iterator, iterable, generator, spread operator
함수형 프로그래밍과 javascript es6
- 앞서 블로그는 es5를 기반으로 작성하였다.
- es6 이후부터는 iterator, iterable, generator, spread operator 등 함수형 프로그래밍을 더 강력하게 만들어줄 기능이 추가되었다.
- es5에서 구현한 함수를 es6에 맞춰 재작성 하였다.
전개 연산자 Spread Operator
- es6부터 전개 연산자가 생겼다.
- 컬렉션 map과 set이 생겼다.
- 전개연산자와 사용하면 set, map을 손쉽게 array로 변환할 수 있다.
- https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Spread_syntax
const log = console.log;
let arr = [1,2,3,4];
let set = new Set([5,6,7,8]);
let map = new Map([['name', 'kim'], ['age', 10]]);
const combined = ['a', 'b', ...arr, ...set, ...map.keys(), ...map.values()];
log(combined);
구조 분해
- 전개 연산자를 역전시켜서, 배열로부터 변수에 값을 부여할 수 있다. 이를 구조 분해라 한다.
- a, b는 첫 번째 인자와 두 번째 인자를 의미한다.
...args
는 3번째부터 마지막까지를 의미한다.
const [a, b, ...args] = [1,2,3,4,5,6,7];
log(a);
log(b);
log(args);
iterable 과 iterator
- 함수형 프로그래밍으로의 기능을 극대화하기 위하여
for...i
구문이 아닌for...of
구문을 신설하였다. -
for...i
구문은 기본적으로 평가를 즉각 수행한다. iterator는 요청할 때마다 값을 하나씩 꺼내는 형태로 지연 평가를 구현하는 것에 유리하다. - iterable한 객체는 [Symbol.iterator] 를 구현한다.
- array, map, set 은 iterable 하다. document.querySelector의 출력값 역시 iterable하다.
- map은 keys, values, entries로 iterator를 출력할 수 있다.
const log = console.log;
const arr = [1,2,3,4];
const set = new Set([5,6,7,8]);
const map = new Map([['name', 'kim'], ['age', 10]]);
for (const i of arr[Symbol.iterator]()) log(i);
for (const i of set[Symbol.iterator]()) log(i);
for (const i of map.keys()[Symbol.iterator]()) log(i);
for (const i of map.values()[Symbol.iterator]()) log(i);
for (const i of map.entries()[Symbol.iterator]()) log(i);
const all = document.querySelectorAll('*');
// for...of와 for...i 의 결과는 동일하다.
for (const e of all) log(e);
for (let i = 0; i < all.length; i++) log(all[i]);
사용자 정의 iterable
- Symbol.iterator를 통해 iterator를 구현할 수 있다.
next()
와[Symbol.iterator]()
함수를 구현한다.
const count3 = {
[Symbol.iterator]() {
let i = 3; // 클로져로서 값을 가진다.
return {
next(){ //
return i == 0? {done : true} : {value : i--, done :false}
},
[Symbol.iterator]() { // 잘 만들어진 iterator는, iterator를 리턴할 때 자기 자신을 리턴할 수 있다.
return this;
}
}
}
}
for (const c of count3) log(c); // iterator 동작
for (let i = 0; i < count3.length; i++) log(count3[i]); // for i 는 동작하지 않네.
let iter1 = count3[Symbol.iterator]();
log(iter1.next());
log(iter1.next());
log(iter1.next());
log(iter1.next());
let iter2 = count3[Symbol.iterator]();
log (iter2.next()); // 하나를 이미 사용했다.
for (const i of iter2) log(i); // 나머지 두 개만 동작한다.
let iter3 = count3[Symbol.iterator]();
let iterOfIter3 = iter3[Symbol.iterator](); // 자기 자신을 리턴한다.
log(iter3===iterOfIter3); // true
log(iter3.next());
log(iterOfIter3.next());
log(iter3.next());
log(iterOfIter3.next()); // done : true
generator
- 평선 예약어 뒤에 *에 넣어(
function* get(){}
) iterator 함수를 구현할 수 있다. 이를 제너레이커 함수라 한다. - yield에 정의한 값을 리턴한다.
- 더 이상 값을 찾을 수 없거나
return
을 반환하면 종료한다.
let genIter = gen();
for (const i of genIter) log(i);
function* odd(l){
for (let i = 0; i < l; i++) {
if(i%2==1) yield i;
if(i==l) return;
}
}
let odd10 = odd(10);
// odd 함수를 보면 for...i에서 한 번씩 누락한다. 그러므로 나는 exist - null - exist - null의 형태로 출력할 줄 알았다.
// 실제로는 다음과 같이 출력한다 :
// Object {value: 1, done: false}
// Object {value: 3, done: false}
// Object {value: 5, done: false}
// Object {value: 7, done: false}
// Object {value: 9, done: false}
// Object {value: undefined, done: true}
// Object {value: undefined, done: true}
for (let i = 0; i < 7; i++) log(odd10.next());
generator의 조합으로 함수형 프로그래밍 하기
- generator을 조합하여 사용할 경우 좀 더 간결하고 좋은 코드를 작성할 수 있다.
function* infinity(start = 0){
while(true) yield start++;
}
let two2Infinity = infinity(2);
for (let i = 0; i < 3; i++) log(two2Infinity.next()); // 2부터 1씩 가산하여 5까지
function* limit(l, iter){
for (const i of iter) {
yield i;
if(i==l) return;
}
}
let iter5to10 = limit(10, infinity(5));
for (const i of iter5to10) log(i); // 5, 6, 7, 8, 9, 10
function* odd(start, end){
for (const i of limit(end, infinity(start))) {
if(i%2==1) yield i;
}
}
let odd2To8 = odd(2, 8);
for (const o of odd2To8) log(o); // 3, 5, 7
iterator를 활용한 함수형 프로그래밍의 기본적인 함수 구현 : map, filter, reduce
- iterator를 활용하면 이전보다 발전된 형태의 함수를 구현할 수 있다.
- map, filter, reduce를 구현하였다.
// 공통으로 사용하는 값
const log = console.log;
const products = [
{name: '반팔티', price: 15000},
{name: '긴팔티', price: 20000},
{name: '핸드폰케이스', price: 15000},
{name: '후드티', price: 30000},
{name: '바지', price: 25000}
];
curry, map
const curry = f => (a, ..._) => _.length? f(a, ..._) : (..._) => f(a, ..._);
const map = curry((f, iter) => {
let res = [];
for (const a of iter) {
res.push(f(a));
}
return res;
});
log(map(p => p.name, products));
log(map(p => p.price, products));
// querySelectorAll은 iterator가 구현되어 있다. map을 사용할 수 있다.
log(map(el => el.nodeName, document.querySelectorAll('*')));
// 구조 분해를 아래와 같이 적극적으로 활용할 수 있다.
let m2 = new Map(
map(
([k, a]) => [k, a * a],
new Map([['a', 10], ['b', 20]])
)
);
- 참고로 es6에서는 map 메서드가 존재하나 현 상황에서는 사용하지 않는다. array-like에서 동작하지 않기 때문이다.
log([1,2,3].map(a => a * a));
log(document.querySelectorAll('*').map(a => a * a)); // 동작하지 아니함.
filter
const filter = curry((f, iter) => {
let res = [];
for (const a of iter) {
if(f(a)) res.push(a);
}
return res;
});
log(map(p=>p.name, filter(p=>p.price > 15000, products)));
log(map(p=>p.name, filter(p=>p.price <= 15000, products)));
log(filter(
a => a % 2 == 0,
function * (){
yield 1 ;
yield 3 ;
yield 10 ;
}())
)
reduce
const reduce = curry((f, acc, iter) => {
if(!iter) {
iter = acc[Symbol.iterator]();
acc = iter.next().value;
}
for (const a of iter) {
acc = f(acc, a);
}
return acc;
});
go
const go = (...args) => reduce((a, f)=>f(a), args);
go(
0,
a => a + 1,
a => a + 10,
a => a + 100,
log
);
pipe
- 함수 리스트를 클로져로 만들고, 해당 클로저에 인자를 삽입하여 평가한다.
- 아래는 다소 복잡한데
- 함수를 두 개로 분리한다.
- 첫 번째 함수(f)는 인자를 여러 개(…as) 받을 수 있다.
- f(…as)의 결과값은 하나이며 이를 go 함수를 통해 처리한다.
const pipe = (f, ...fs) => (...as) => go(f(...as), ...fs);
const f_pipe2 = pipe(
(a, b) => a + b,
a => a + 10,
a => a + 100
);
log(
f_pipe2(3, 7)
); // 120
go를 사용하기
const total_price = pipe (
map(p=>p.price),
reduce((a, b) => a + b)
);
const base_total_price = predi => pipe(
filter(predi),
total_price,
);
go(
products,
base_total_price(p=>p.price<20000),
console.log
);
다음의 수업을 학습하고 블로그를 정리하였습니다. 매우 강추 합니다! : https://www.inflearn.com/course/functional-es6