JavaScript-함수형 언어 (2)

3 분 소요

Array, Set, Map을 통한 iterable/iterator 프로토콜

  • 자바스크립트엔 Array, Set, Map이라는 내장객체들이 존재한다.
  • 이 객체들을 전부 Symbol.iterator를 통해 for…of 구문으로 순회할 수 있다.
  • 만약 arr[Symbol.iterator]를 null로 한 다음 순회를 하려하면 순회를 할 수 없다.
  • Symbol.iterator는 순회를 도와주는 함수이다.
// Array
const arr = [1, 2, 3];
for (const a of arr) console.log(a);  // 1
                                      // 2
                                      // 3

// Set
const set = new Set([1, 2, 3]);
for (const a of set) console.log(a);  // 1
                                      // 2
                                      // 3

// Map
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
for (const a of map) console.log(a);  // ["a", 1]
                                      // ["b", 2]
                                      // ["c", 3]
  • 하지만 여기서 set이나 map은 set[0] 또는 map[0]으로 조회가 불가능(undefined)하다.
  • 단순 배열과 같은 순회방식이 아니기 때문이다.
  • iterable : 이터레이터를 리턴하는 [Symbol.iterator]() 를 가진 값 (여기선 arr, set, map)
  • iterator : { value, done } 객체를 리턴하는 next() 메소드를 가진 값
  • iterable / iterator 프로토콜 : iterable을 for…of, 전개연산자 등으로 동작하도록한 규약
  • 그래서 set이나 map은 iterable / iterator 프로토콜 규약을 따르기 때문에 for…of문으로 순회가 가능한 것이다.
  • 아래 코드는 위의 코드의 결과와 똑같다.
const arr = [1, 2, 3];
let iterator1 = arr[Symbol.iterator]();
for (const a of iterator1) console.log(a);

const set = new Set([1, 2, 3]);
let iterator2 = set[Symbol.iterator]();
for (const a of iterator2) console.log(a);

const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
let iterator3 = map[Symbol.iterator]();
for (const a of iterator3) console.log(a);
for (const a of map) console.log(a);
for (const a of map.entries()) console.log(a);
for (const a of map.keys()) console.log(a);
for (const a of map.values()) console.log(a);

사용자 정의 iterable 프로토콜

  • 3, 2, 1을 조회하는 iterable 프로토콜을 직접 정의하기
  • iterator의 next()함수를 이용해서 진행한 후에 순회를 할 수도 있다.
let iterable = {
  [Symbol.iterator]() {
    let i = 3;
    return {
      next() {
        return i === 0 ? { done : true} : { values : i--, done : false }  
      },
      [Symbol.iterator]() { return this; }  // well-formed iterator의 조건
    }
  }
};

let iterator = iterable[Symbol.iterator](); 

for (const a of iterator) console.log(a); 
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
  • iterator가 자기 자신을 반환하는 Symbol.iterator를 가지고 있을 때 이게 잘 정의된 well-formed iterator라고 할 수 있다.
let arr = [4, 5, 6];
let iterator = arr[Symbol.iterator]();
if (iterator[Symbol.iterator]() === iterator) console.log('well-formed iterator');

전개 연산자

  • 전개 연산자로도 iterable 순환조회가 가능하다.
  • 중간에 arr[Symbol.iterator] = null; 을 추가하면 arr은 iterator가 아니므로 순환조회할 수 없다고 에러가 뜬다.
const arr = [4, 5, 6, 7];
const set = new Set([1, 2, 3]);
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);

console.log(...arr);
console.log(...set);
console.log(...map);
console.log(...map.keys());
console.log(...map.values());
console.log(...arr, ...set, ...map.values(), ...[10, 11]);

/*
4 5 6 7
1 2 3
[ 'a', 1 ] [ 'b', 2 ] [ 'c', 3 ]
a b c
1 2 3
4 5 6 7 1 2 3 1 2 3 10 11
*/

generator

  • generator : iterator이자 iterable을 반환하는 함수
  • generator가 반환하는 iterator는 well-formed iterator이다.
  • generator는 순회할 값을 문장으로 표현하는 것이라고도 할 수 있다.
  • javascript에선 어떠한 값이든 그게 iterable이라면 뭐든 순회할 수 있다.
  • 그런데 generator는 문장을 통해서 순회할 수 있는 값을 만들 수 있다.
  • 따라서 generator는 어떠한 상태는 어떠한 값이든 순회할 수 있게 만들어 버린다.
function *gen() {
  yield 1;
  if (false) yield 2;
  yield 3;
  return 100;
}

let iterator = gen();  // generator는 iterator를 반환한다.
console.log(iterator[Symbol.iterator]() === iterator);
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());

for (const a of gen()) console.log(a);

/*
true
{ value: 1, done: false }
{ value: 3, done: false }
{ value: 100, done: true }
{ value: undefined, done: true }
1
3
*/

odds - 홀수를 순회하는 제네레이터 만들기

function *infinite(i = 0) {
  while (true) yield i++;
}

function *limit(n, iter) {
  for (const a of iter) {
    yield a;
    if (a === n) return;
  }
}

function *odds(n) {
  for (const a of limit(n, infinite(1))) {
    if (a % 2) yield a;
  }
}

let iteraotr = odds(10);
console.log(iteraotr.next());
console.log(iteraotr.next());
console.log(iteraotr.next());
console.log(iteraotr.next());
console.log(iteraotr.next());
console.log(iteraotr.next());

/*
{ value: 1, done: false }
{ value: 3, done: false }
{ value: 5, done: false }
{ value: 7, done: false }
{ value: 9, done: false }
{ value: undefined, done: true }
*/

for…of, 전개 연산자, 구조 분해, 나머지 연산자를 이용한 순환구조

console.log(...odds(14));
console.log(...odds(14), ...odds(20));

const [head, ...tail] = odds(10);
console.log(head);
console.log(tail);

const [a, b, ...rest] = odds(20);
console.log(a);
console.log(b);
console.log(rest);

/*
1 3 5 7 9 11 13
1 3 5 7 9 11 13 1 3 5 7 9 11 13 15 17 19
1
[ 3, 5, 7, 9 ]
1
3
[ 5, 7, 9, 11, 13, 15, 17, 19 ]
*/

카테고리:

업데이트:

댓글남기기