Skip to content
Underbleu
GithubLinkedin

well-formed 이터러블의 장점 (feat. 피보나치수열)

Functional programming1 min read

프로그래머스에서 진행한 유인동님의 ES6로 알아보는 동시성 & 함수형 프로그래밍 강의를 들으며 질문/답변에 대한 추가학습

Q: well-formed iterable이 가지는 장점이 있을까요 ?

제너레이터로 생성한 이터러블만 자기자신을 반환하는 well-formed이고, 내장 iterator를 가지고 있는 "Array, String, NodeList..." 같은 애들은 well-formed가 아닌것 같은데...

well-formed iterable이 가지는 장점이 있을까요 ?

1var iterG = valuesIter({ a: 1, b: 2 });
2var arr = [ 1, 2 ];
3var str = "abc";
4var node = document.querySelectorAll("div");
5var map = new Map([["a", 1],["b", 2]]);
6var set = new Set([1, 2, 3]);
7
8iterG[Symbol.iterator]() == iterG // true
9arr[Symbol.iterator]() == arr // false
10str[Symbol.iterator]() == str // false
11node[Symbol.iterator]() == node // false
12map[Symbol.iterator]() == map // false
13set[Symbol.iterator]() == set // false

A: 선생님 답변

  • 일단, well-formed의 장점은 여기저기서 조합하기 좋다
  • Array, String은 well-formed가 맞음. 위의 테스트가 잘못되었음
1// 올바른 Test Case
2var arr = [1,2,3];
3arr[Symbol.iterator]() == arr // false
4
5var arrIter = [1,2,3][Symbol.iterator]();
6arrIter[Symbol.iterator]() == arrIter; // true

-> Array의 [Symbol.iterator] 메서드를 한번 실행시켜줘야 이터레이터가 된다 !
-> 이터레이터가 된 후와 비교해야 올바른 테스트!

collIter()함수의 코드가 이 원리와 같다

  1. coll이 [Symbol.iterator]를 가지고 있다면
  2. 이를 실행시켜줘야 -> 이터레이터가 된다
  3. 제너레이터는 호출하는 순간 실행 -> 이터레이터가 된다
1const collIter = coll =>
2 hasIter(coll) ? // --> 1
3 coll[Symbol.iterator]() : // --> 2
4 valuesIter(coll); // --> 3

Well-formed Iterable이 유용한 이유

Iterators that are Iterable
Why is it useful if an iterator is also an iterable? for-of only works for iterables, not for iterators. Because Array iterators are iterable, you can continue an iteration in another loop

  • Iterator이면서 Iterable인 객체를 잘정의된(well-formed) Iterable이라고 부른다
  • 제너레이터가 만들어주는 제너레이터객체는 well-formed Iterable
  • This is Why ES6 generators are usually much more convenient !

예시) 피보나치 수열을 생성하는 이터러블

  1. non well-formed
    이터러블이 진행된 지점을 기억하지 못해 처음부터 Restart
  2. well-formed 이터러블이 진행된 지점을 기억하고 그 지점부터 Restart
1//1. non well-formed
2
3var fibonacci = {
4 [Symbol.iterator]() {
5 let [prev, curr] = [0, 1];
6 let step = 0;
7 const maxStep = 5;
8 return {
9 next() {
10 [prev, curr] = [curr, prev + curr];
11 return { value: curr, done: step++ >= maxStep };
12 }
13 };
14 }
15};
16
17// Test -----
18
19for(const v of fibonacci) {
20 console.log(v);
21 break;
22} // 1
23
24for(const v of fibonacci) {
25 console.log(v);
26} // 1 2 3 5 8 -> Restart iterator
27
28
29
30//2. well-formed
31
32var fibonacciW = {
33 prev: 0, curr: 1, step: 0, maxStep: 5,
34 next() {
35 [this.prev, this.curr] = [this.curr, this.prev + this.curr];
36 return { value: this.curr, done: this.step++ >= this.maxStep };
37 },
38 [Symbol.iterator]() {
39 console.log(this)
40 return this;
41 }
42};
43
44// Test -----
45
46for(const v of fibonacciW) {
47 console.log(v);
48 break;
49} // 1
50
51for(const v of fibonacciW) {
52 console.log(v);
53} // 2 3 5 8 -> Continue with same iterator

# 결론

  • Array, String, Map, Set, NodeList는 [Symbol.iterator]를 내장메서드로 가지고 있기때문에 기본적으로 Iterable하다
  • 하지만 well-formed Iterable로 동작하기 위해선 [Symbol.iterator]()를 한 번 실행시켜줘야한다
1var arr = [1,2,3]; //--> Iterable
2var arrIt = arr[Symbol.iterator](); // well-formed Iterable
3
4//1. Iterable
5for(const v of arr) {
6 console.log(v);
7 break;
8} // 1
9
10for(const v of arr) {
11 console.log(v);
12} // 1 2 3 --> 다시 시작
13
14//2. well-formed Iterable
15
16for(const v of arrIt) {
17 console.log(v);
18 break;
19} // 1 --> 순회한 위치를 기억
20
21undefined
22for(const v of arrIt) {
23 console.log(v);
24} // 2 3 -> Continue with Same iterator