#TIL_21.8.17

ISOJ·2021년 8월 17일
1

Today I Learned

목록 보기
9/43
post-thumbnail

전개연산자의 이터레이터 프로토콜

  • 전개연산자도 이터레이터 프로토콜을 따름
  • ...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

    자바스크립트는 제너레이터를 통해서 어떠한 상태든, 어떠한 값이든 순회할 수 있게 만들어준다.

제너레이터의 odd

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, 전개 연산자, 구조 분해, 나머지 연산자 를 사용할 수 있다!

map

const map = (f, iter) => {
	let res = []
    for (const a of iter) {
    	res.push(f(a))
    }
    return res
}

map(p => p.name, product)

이터러블 프로토콜을 따른 map 의 다형성

  • 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

  • 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]

reduce

  • 값을 축약하는 함수 ex) 특정 값을 누적하면서, 하나의 값으로 만들어냄
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)

map + filter + reduce 중첩 사고와 함수형 사고

  • ex)
const add = (a, b) => a + b

reduce(add, map(p => p.price, filter(p => p.price < 20000, products )))
  • 해석할 때는 오른쪽부터 왼쪽으로 읽으면 이해하기 쉽다. (filter -> map -> reduce)
  • 순서는 적절히 만들어보자.

회고

함수형 프로그래밍은 아직도 감이 잘 잡히지 않는다.. map, filter, reduce 는 코딩테스트에서도 종종 사용하는 함수였는데 그 내부에서 어떤 로직으로 값을 출력하는지 알수 있는 시간이었다.

아직 함수들을 중첩으로 조합하여 원하는 값을 출력하는게 어렵다. 이부분은 많은 연습이 필요할 것 같다.

제너레이터와 이터레이터의 개념도 한번 더 짚고 넘어가야겠다.

profile
프론트엔드

0개의 댓글