...arr
, ...set
, ...map
, ...map.keys()
, ... 의 방식으로 사용할 수 있음 function *gen() { ... }
와 같이 함수명 앞에 *
을 붙여 만듬function *gen() {
yield 1
yield 2
yield 3
return 100
}
let iter = gen()
console.log(iter.next()) // value: 1, done: false
console.log(iter.next()) // value: 2, done: false
console.log(iter.next()) // value: 3, done: false
console.log(iter.next()) // value: 100 (return 100), done: true
if
문을 사용 가능하다.if (false) yield 2
자바스크립트는 제너레이터를 통해서 어떠한 상태든, 어떠한 값이든 순회할 수 있게 만들어준다.
function *odds(num) {
for (let i = 0; i < num; i++) {
if (i % 2) yield i
}
}
let iter = odds(10)
iter.next() // value: 1
iter.next() // value: 3
iter.next() // value: 5
iter.next() // value: 7
iter.next() // value: 9
iter.next() // value: undefined
function *infinity(i = 0) {
while (true) yield i++
}
/*
위 제너레이터를 통한 이터레이터는 무한히 while 반복문이 돌지만, next() 를 실행할 때만 그 다음 value를 반환하기 때문에 프로그램이 멈추지 않는다.
let iter = infinity()
iter.next() // value: 1
iter.next() // value: 2
iter.next() // value: 3
iter.next() // value: 4
iter.next() // value: 5
iter.next() // value: 6
*/
function *limit(l, iter) {
for (const a of iter) {
yield a
if (a == l) return
}
}
/*
이터러블을 받아, 이터러블 안의 값을 yield 하다가, l (limit) 과 같은 값을 만났을 때 return
limit(4, [1, 2, 3, 4, 5, 6])
*/
function *odds(num) {
/*
for (const a of infinity(num)) {
if (a % 2) yield a
if (a == num) return
}
*/
for (const a of limit(num, infinity(1)) {
if (a % 2) yield a
if (a == num) return
}
}
let iter = odds(10)
iter.next() // value: 1
iter.next() // value: 3
iter.next() // value: 5
iter.next() // value: 7
iter.next() // value: 9
iter.next() // value: undefined
...
제너레이터는 for ... of, 전개 연산자, 구조 분해, 나머지 연산자 를 사용할 수 있다!
const map = (f, iter) => {
let res = []
for (const a of iter) {
res.push(f(a))
}
return res
}
map(p => p.name, product)
map
은 이터러블 프로토콜을 따르기 때문에 다형성이 높다.function *gen() {
yield 2
yield 3
yield 4
}
map(a => a * a, gen()) // [4, 9, 16]
let m = new Map()
m.set ('a', 10) // ['a', 10]
m.set ('b', 20) // ['b', 20]
map(([key, value]) => [key, value * 2], m) // 0: ['a', 20] 1: ['b', 40]
or
new Map(map(([key, value]) => [key, value * 2], m)) // 0: ['a', 20] 1: ['b', 40]
filter
특정 값에 해당하는 것들만 걸러냄const filter = (f, iter) => {
let res = []
for (const a of iter) {
if (f(a)) res.push(a)
}
return res
}
...filter(p => p.price < 20000, products)
filter(n => n % 2, [1, 2, 3, 4]) // [1, 3]
filter(n => n % 2, function *() {
yield 1
yield 2
yield 3
yield 4
yield 5
} ())
// [1, 3, 5]
const reduce = (f, acc, iter) => {
if (!iter) {
iter = acc[Symbol.iterator]()
acc = iter.next().value
}
for (const a of iter) {
acc = f(acc, a)
}
return acc
}
const add = (a, b) => a + b
reduce(add, 0, [1, 2, 3, 4, 5]) // 15
// acc 는 생략이 가능하다. 생략시, 위 코드는 아래와 같이 동작한다.
reduce(add, 1, [2, 3, 4, 5]) // 15
객체 데이터가 존재할 때...
reduce((total_price, product) => total_price + product.price, 0, products)
const add = (a, b) => a + b
reduce(add, map(p => p.price, filter(p => p.price < 20000, products )))
함수형 프로그래밍은 아직도 감이 잘 잡히지 않는다.. map, filter, reduce 는 코딩테스트에서도 종종 사용하는 함수였는데 그 내부에서 어떤 로직으로 값을 출력하는지 알수 있는 시간이었다.
아직 함수들을 중첩으로 조합하여 원하는 값을 출력하는게 어렵다. 이부분은 많은 연습이 필요할 것 같다.
제너레이터와 이터레이터의 개념도 한번 더 짚고 넘어가야겠다.