함수형 프로그래밍과 JavaScript ES6+ 3. 제너레이터와 이터레이터

|

inflearn의 함수형 프로그래밍과 JavaScript ES6+를 보고 공부한 것을 정리합니다.

제너레이터/이터레이터

  • 제너레이터 : 이터레이터이자 이터러블을 생성하는 함수 -> 즉, 이터레이터를 리턴하는 함수
    const log = console.log;
    function *gen() { // 일반함수에 별을 붙여서
        yield 1;
        if (false) yield 2; // 제너레이터는 순회할 값을 문장으로 표현하는 것이라고도 말할 수 있다
        yield 3;
        return 100;
    }
    let iter = gen(); // 제너레이터를 실행한 결과는 이터레이터이자 이터러블
    log(iter[Symbol.iterator]() == iter); // true -> 제너레이터는 well-formed iterator를 리턴
    log(iter.next()); // Object -> {value: 1, done: false}
    log(iter.next());
    log(iter.next());
    log(iter.next()); // 리턴값은 done일 때에 출력됨
    // 제너레이터를 통해 쉽게 이터레이터를 생성 가능

    for (const a of gen()) log(a); // 순회할 때는 리턴값 없이 순회가 이루어짐
  • 일반함수에 *를 붙여서 제너레이터를 생성
  • 제너레이터를 실행한 결과는 이터레이터이자 이터러블 (well-formed) -> 제너레이터를 통해 쉽게 이터레이터를 생성 가능
  • 리턴값은 done일 때에 출력됨
  • 다만 순회할 때에는 리턴 값 없이 순회가 이루어진다.
  • 제너레이터는 순회할 값을 문장으로 표현하는 것이라고 말할 수 있다.
  • 이 제너레이터를 통해 어떠한 값도 순회할 수 있는 형태로 조작할 수 있다. -> 자바스크립트의 높은 다형성

odds

제너레이터를 활용하여 홀수만 계속해서 발생시키는 이터레이터를 만들어 순회하는 연습

  1. 가장 간단한 홀수 발생 제너레이터
     function *odds(limit) {
         for (let i = 0; i< limit; i++) {
             if (i % 2) yield i;
         }
     }
     let iter2 = odds(10);
     log(iter2.next());
     log(iter2.next());
     log(iter2.next());
     log(iter2.next());
     log(iter2.next());
    
  2. 무한 발생 제너레이터
     function *infinity(i = 0) {
         while (true) yield i++;
     }
     let iter3 = infinity();
     log(iter3.next());
     log(iter3.next());
     log(iter3.next());
     log(iter3.next());
     log(iter3.next());
    
    • 무한하긴 하지만 next할 때만 동작하기 때문에 메모리를 많이 사용하지 않는다.
  3. 중첩된 제너레이터
     function *odds(limit) {
         for (const a of infinity(1)) {
             if (a % 2) yield a;
             if (a == limit) return;
         }
     }
     // 중첩도 가능
     let iter4 = odds(10);
     log(iter4.next());
     log(iter4.next());
     log(iter4.next());
     log(iter4.next());
     log(iter4.next());
    
  4. limit과 또다른 이터러블을 인자로 받는 제너레이터
     function *limit(limit, iter) { // iterable을 받아서 계속해서
         for (const a of iter) { // iter안의 값을 yield하다가
             yield a;
             if (a == limit) return; // limit과 같은 값을 만나면 더이상 돌지 않도록
         }
     }
     let iter5 = limit(4, [1, 2, 3, 4, 5, 6]);
     log(iter5.next());
     log(iter5.next());
     log(iter5.next());
     log(iter5.next());
     log(iter5.next());
    
  5. *limit을 활용한 홀수 생성 제너레이터
     function *odds(l) {
         for (const a of limit(l, infinity(1))) {
             if (a % 2) yield a;
         }
     }
    
     for (const a of odds(40)) log(a);
    

제너레이터를 활용하여 문장으로 만들어진 것들을 순회한다거나 할 수 있도록 값으로 문장을 다룰 수 있다.

for of, 전개 연산자, 구조 분해, 나머지 연산자

제너레이터도 이터레이터/이터러블 프로토콜을 따르고 있기 때문에 그 프로토콜을 따르는 문법들, 라이브러리, 함수들과 함께 잘 사용될 수 있다.

전개 연산자

log(...odds(10));
log([...odds(10), ...odds(20)]);

구조 분해

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

나머지 연산자

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

이처럼 제너레이터, 이터레이터를 활용하여 좀더 조합성이 높은 프로그래밍을 할 수 있음

200303TIL

|

오늘 한 것

  • 자바스크립트 함수형 프로그래밍 제너레이터와 이터레이터 수강하고 정리
  • 자소서 하나 쓰기

회고

제너레이터를 작년 여름 처음 접했을 때만 해도 너무나 넓은 응용의 폭에 비해 사용법은 너무나 간단하고 (그때의 나에게는) 추상적이어서 꽤 헤맸던 기억이 난다. 파이썬의 공식 문서나, 여러 포스팅을 찾아보며 고군분투했었는데 자바스크립트에서 다시 접하니 그때의 축적된 경험 덕에 비교적 쉽게 이해가 된다. 공부와 학습은 역시 쌓여가는구나.

함수형 프로그래밍과 JavaScript ES6+ 2. ES6에서의 순회와 이터러블:이터레이터 프로토콜

|

inflearn의 함수형 프로그래밍과 JavaScript ES6+를 보고 공부한 것을 정리합니다.

기존과 달라진 ES6에서의 리스트 순회

  • 대부분의 프로그래밍에서 (실무에서도) 리스트 순회는 굉장히 중요
  • 자바스크립트가 ES6이 되면서 리스트 순회에 많은 변경점 존재
  • 깊이 들여다보면 언어적으로 큰 발전
  • for i++
  • for of

기존 ES5에서의 리스트 순회

  const list = [1, 2, 3];
  for (var i = 0; i < list.length; i++) {
    log(list[i]);
  }

array의 length라는 프로퍼티에 의존하여 숫자라는 key로 순회하도록 i를 증가시켜주면서 숫자 key로 접근하여 array 안의 값을 순회

유사배열 역시 동일한 방식으로 순회

  const str = 'abc';
  for (var i = 0; i < str.length; i++) {
    log(str[i]);
  }

ES6에서의 리스트 순회는 이렇게 바뀜

  const list = [1, 2, 3];
  for (const a of list) {
    log(a);
  }
  const str = 'abc';
  for (const a of str) {
    log(a);
  }
  • 간결해진 문법
  • 어떻게 순회하는지를 명령적으로 기술하기보다 보다 선언적으로
  • 복잡한 for문을 좀더 간결하게 만든 것 이상의 의미들이 있음
  • ES6이라는 언어가 순회에 대해 추상화한 것

Array, Set, Map을 통해 알아보는 이터러블/이터레이터 프로토콜

  • array, set, map을 모두 for문으로 순회 가능
  • 각 공통점, 차이점을 살펴보면서 ES6에서 for문이 어떻게 동작하고 어떻게 추상화되어 있는지 알아봄

Array를 통해 알아보기

 const arr = [1, 2, 3];
 for (const a of arr) log(a);

Set을 통해 알아보기

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

Map을 통해 알아보기

  const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
  for (const a of map log(a);
  • array는 key로 안에 있는 값을 조회할 수 있다.
  • 그러나 set과 map은 key로 조회가 불가능
  • 즉 for of의 내부문이 for (var i = 0; i < str.length; i++) 이렇게 생기지 않았다는 뜻

Symbol.iterator

  • ES6에서 추가된, 말 그대로 Symbol
  • 이 Symbol은 어떤 객체의 key로 사용될 수 있다.
  • log(arr[Symbol.iterator])를 출력해 보면, 어떤 함수가 출력된다.
  • arr[Symbol.iterator] = null; 함으로써 이 함수를 비워보고 다시 for of 순회를 하게 되면 Uncaught TypeError: arr is not iterable at ... 이라는 에러가 뜬다.
  • 즉, arr가 iterable할 수 없다는 에러. -> for of문과 Symbol.iterator에 담겨져 있는 함수가 연관이 있다는 뜻
  • Set이나 Map도 마찬가지!

이터러블/이터레이터 프로토콜

  • Array, Set, Map은 자바스크립트 내장 객체로서 이 프로토콜을 따른다.
  • 이터러블 : 이터레이터를 리턴하는 Symbol.iterator 를 가진 값
    • arr는 이터러블이라고 할 수 있음
    • arr의 Symbol.iterator를 지워서 에러가 날 때 arr가 이터러블이 아니라는 에러가 출력되었었음.
    • 에러가 난 이유는 이터러블을 충족시키는 이터레이터 메서드를 지웠기 때문임!
    • 그리고 이 arr[Symbol.iterator]() 메서드를 실행했을 때는 Array 이터레이터를 리턴한다.
  • 이터레이터 : { value, done } 객체를 리턴하는 next() 를 가진 값
    • 이터레이터는 next() 를 가졌음
    • next() 를 실행시켰을 때 value와 done을 가지는 객체를 리턴
    • 위의 Array iterator에 대해서 next()를 출력하면 다음과 같음
    views
    Array에서 iterator의 출력
    • 계속해서 next()를 출력하게 되면 어느 시점부터는 done이 true이고 value가 undefined로 출력된다.
  • 이터러블/이터레이터 프로토콜: 이터러블을 for…of, 전개 연산자 등과 함께 동작하도록 한 규약
    • for…of문에서는 value에 들어오는 값을 a에 담아서 계속 출력하다가, done이 true가 되면 이 for문에서 빠져나오도록 되어 있다.
  const arr = [1, 2, 3];
  let iter1 = arr[Symbol.iterator]();
  iter1.next(); // 1
  iter1.next(); // 2
  iter1.next(); // 3
  const arr = [1, 2, 3];
  let iter1 = arr[Symbol.iterator]();
  for (const a of iter1) log(a); // 1, 2, 3

Set이 key값이 없음에도 for of 문에서 동작하는 이유는 Set이 이터러블/이터레이터 프로토콜을 따르고 있기 때문.

views
Set에서 iterator의 출력
views
Map에서 iterator의 출력
  • Map의 iterator에는 value에 또다른 Array에 담겨 있음
    const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
    var iter2 = map[Symbro.iterator]();
    for (const a of iter2) log(a);
    

    -> for…of map 문과 동일한 출력

  • ‘map.keys()’ -> MapIterator를 출력 -> map의 key만 출력 가능 (‘a’, ‘b’, ‘c’)
  • ‘map.values()’ -> MapIterator를 출력 -> value만 출력
  • ‘map.entries()’ -> key와 value를 함께 출력
  • MapIterator 역시 Symbol.iterator를 가지고 있음.
  • 즉 keys, values, entries 메서드는 이 Symbol.iterator가 또다시 반환하는 이터레이터를 가지고 순환한다.

사용자 정의 이터러블, 이터러블/이터레이터 프로토콜 정의

  const iterable = { // for문으로 호출했을 때 value로 3, 2, 1을 리턴해주고 끝나는 사용자 정의 이터러블 구현
    [Symbol.iterator]() { // Symbol.iterator 메서드를 구현하고 있어야
      let i = 3;
      return { // 이터레이터를 반환
        next() { // 이터레이터는 next를 메서드로 가지고 있음
          return i == 0 ? { done: true} { value: i--, done: false } // next는 value와 done을 가지고 있는 객체를 리턴
        }
      }
    }
  };
  let iterator = iterable[Symbor.iterator]();
  // log(iterator.next()); // next를 통해 조회 가능
  // log(iterator.next());
  // log(iterator.next());
  // log(iterator.next());
  for (const a of iterable) log(a); // for..of를 통해 순회 가능
views
사용자 정의 이터러블에서 next()의 출력
views
사용자 정의 이터러블에서 for...of의 출력
  • iterable에 Symbor.iterator가 구현되어 있기 때문에 for..of 문에 들어갈 수 있음
  • 이터레이터 객체를 반환
  • 내부적으로 next()를 실행하면서 value를 담게 됨
  • 아직은 자바스크립트 이터레이터 이터러블의 모든 속성을 구현하지는 못했음.
  const arr2 = [1, 2, 3];
  let arr2[Symbol.iterator]();
  iter2.next(); // 일부 진행했을 때 진행한 이후의 값으로만 순회가 가능
  for (const a of arr2) log(a);

  • 잘 구현된 이터러블은 이터레이터를 만들었을 때 진행 도중 순회를 할 수도 있고, 이 이터레이터를 그대로 for .. of문에 넣었을 때 그대로 모든 값을 순회할 수 있도록 되어 있음.
  • 위의 iter2 역시 Symbol.iterator를 가지고 있음
  • 그리고 그를 실행한 결과는 자기 자신
    log(iter2[Symbol.iterator]() == iter2); // true
    
  • 이렇게 이터레이터가 자기 자신을 반환하는 Symborl.iterator를 가지고 있을 때 well-formed iterable, well-formed iterator라고 할 수 있다.
  const iterable = { // for문으로 호출했을 때 value로 3, 2, 1을 리턴해주고 끝나는 사용자 정의 이터러블 구현
    [Symbol.iterator]() { // Symbol.iterator 메서드를 구현하고 있어야
      let i = 3;
      return { // 이터레이터를 반환
        next() { // 이터레이터는 next를 메서드로 가지고 있음
          return i == 0 ? { done: true} { value: i--, done: false }; // next는 value와 done을 가지고 있는 객체를 리턴
        },
        [Symbol.iterator]() { return this; }
      }
    }
  };
  • 이 사용자 정의 이터러블이 well-formed iterator를 반환할 수 있도록 하기 위하여, 자기 자신이 이터레이터이면서 Symbor.iterator()를 리턴했을 때 자기 자신을 반환하도록 하여 다시 한 번 for문에 들어간다거나 할 때 이전까지 진행되었던 자기 자신을 기억하여 그곳으로부터 다시 for문이 진행될 수 있도록 한다.
  • 이미 많은 오픈소스 라이브러리들이나 자바스크립트에서 순회 가능한 형태의 값을 가진 객체들은 대부분 이 iterable/iterator 프로토콜을 따르기 시작하였음.
  • 페이스북에서 만든 immutable js 역시 이 프로토콜을 따른다.
  • 자바스크립트가 사용되고 있는 환경인 브라우저들의 Web API… 돔과 같은 곳에서도 이 프로토콜을 따르고 있다.
<script>
  log(document.querySelectorAll('*')); // 엘리먼트들을 조회한 상태에서
  for (const a of document.querySelectorAll('*')) log(a); // 와 같은 식으로 순회가 가능
  log(all); // 이 값이 배열이어가 아니라,
  log(all[Symbol.iterator]()) // 가 구현되어 있으며 이터레이터를 리턴하기 때문
</script>

전개 연산자

  • 마찬가지로 이터레이터/이터러블 프로토콜을 따른다.
    const a = [1, 2];
    log([...a, ...[3, 4]]); // [1, 2, 3, 4]로 하나의 array가 됨
    
  • 전개연산자 역시 이터러블 프로토콜을 따르고 있는 값들을 펼칠 수 있음
    log([...a, ...arr, ...set, ...map.keys()]);
    
  • 여러 가지를 섞을 수도 있음.
  • 이터러블을 정확히 익히고, 이터러블에서 사용된 추상을 정확히 아는 것이 중요.

강의참고자료

함수형 프로그래밍과 JavaScript ES6+ 1. 함수형 자바스크립트 기본기

|

inflearn의 함수형 프로그래밍과 JavaScript ES6+를 보고 공부한 것을 정리합니다.

평가

  • 코드가 계산(Evaluation) 되어 값을 만드는 것

일급

  • 값으로 다룰 수 있다.
  • 변수에 담을 수 있다.
  • 함수의 인자로 사용될 수 있다.
  • 함수의 결과로 사용될 수 있다.
const a = 10; // 함수를 값을 담음
const add10 = a => a + 10; // 변수에 함수를 담음
const r = add10(a); // 함수의 결과로 사용됨
log(r); // 함수의 인자로 사용됨

일급 함수

  • 자바스크립트에서 함수는 일급
  • 함수를 값으로 다룰 수 있다는 뜻
 const add5 = a => a + 5; // 함수를 값으로 다루어 담음
 log(add5); // 함수에게 전달하여 출력 가능 : a => a + 5 가 출력됨
 log(add5(5)); // 함수의 결과로 만들어 출력될 수 있음 : 10이 출력됨

 const f1 = () => () => 1; // 함수의 결과값으로 함수가 사용될 수 있음
 // f1이라는 함수는 실행되었을 때 다시 함수를 리턴할 수 있음
 log(f1()); // () => 1 이 출력됨. 즉 실행 결과가 함수인 것.

 const f2 = f1(); // 다시 실행 결과를 f2라는 변수에 담음
 log(f2); // () => 1 이 출력됨.
 log(f2()); // 담겨진 함수를 내가 원하는 시점에 평가하여 결과를 만들 수 있다.

자바스크립트에서 함수가 1급이다. -> 조합성과 추상화의 도구로 함수를 사용할 수 있음!

고차 함수

  • 함수를 값으로 다루는 함수
  • 크게 두 가지
    1. 함수를 인자로 받아서 실행하는 함수
    2. 함수를 만들어 리턴하는 함수 (클로저를 만들어 리턴하는 함수)

함수를 인자로 받아서 실행하는 함수

 const apply1 = f => f(1); // 함수를 받아서 함수에 1을 적용하는 함수
 const add2 = a => a+2;
 log(apply1(add2)); // 3으로 평가됨
 log(apply1(a => a - 1)); // 0으로 평가됨

함수를 인자값으로 다루고 있기 때문에 apply1은 고차 함수

 const times = (f, n) => { // 함수 f와 숫자 n을 받아서 n만큼 함수 f를 실행하는 함수
   // 실행하면서 몇 번째 실행되는 중인지도 전달하도록 만들어봄
   let i = -1;
   while (++i < n) f(i);
 }

 times(log, 3); // 3번 실행됨 : 0, 1, 2
 times(a => log(a + 10), 3); // 10, 11, 12

함수를 값으로 받아서 또 다른 값을 받아 안에서 실행하면서 자신이 원하는 인자를 적용하는 함수 -> applicative programming

함수를 만들어 리턴하는 함수 (클로저를 만들어 리턴하는 함수)

  const addMaker = a => b => a + b;
  const add10 = addMaker(10); // b =? b + 10이 저장되어 있음
  log(add10(5)); // 15
  log(add10(10)); // 20
  • 클로저 : 이 함수가 a를 계속해서 기억하고 있다는 뜻
  • 이 함수는 함수이자 a를 기억하는 클로저
  • 클로저 : add10이라는 함수가 만들어질 때의 환경인 a(10)와 b => a + b를 통칭해서 말하는 용어
  • addMaker는 클로저를 리턴하는 함수이고,
  • 함수가 함수를 만들어서 리턴할 때는 결국은 클로저를 만들어서 리턴하기 위해 사용한다.

2월 동안 뭘 했는가? 토익스피킹 lv 올리기

|

토익스피킹 시험을 다시 준비하다

본격적인 취업 시즌도 다가오고, 마음 한켠에 있는 짐을 덜어버리고 싶었다. 작년 하반기 면접에서 연이어 고배를 마신 이유가 어쩌면 낮은 토익스피킹 점수에 있을지도 모른다는 불안감이 항상 마음 한 켠에 존재하고 있었다. 실제로 토익스피킹 점수가 불합격에 영향을 미쳤는지, 그렇지 않았는지는 알 수 없지만 어쨌든 토익스피킹 점수를 올리지 않을 이유가 없었다.

학원

토익스피킹 시험을 처음 본 것은 2019년 2월이었는데, 당시 해커스어학원의 세이임 선생님의 초급, 중급반 강의를 들었었다. 중급반까지 모두 보고 시험을 볼 계획이었으나, 중급반을 다니던 도중 라섹 수술을 하게 되면서 어영부영 학원도 중간부터 빠지게 되고, 2주간의 공백 후 토익 시험을 보게 되었다.

그렇게 보러 간 시험 성적이 당연히 좋을 리 없었다. 110, lv.5 의 등급을 받았지만, 2월도 거의 다 지나가고 있었고 당시에는 개강이 목전에 있었기 때문에 일단은 이걸로도 괜찮지 않을까 하는 마음가짐으로 재시험은 보지 않았다. (지금 생각하면 무슨 자신감이었는지 모르겠다.)

아무튼 그때의 경험을 살려, 토익스피킹은 토익과 같은 시험과는 다르고, 학원이 단기간에 성적을 올리기에는 가장 좋다는 경험을 토대로 하여, 이번에도 해커스 어학원에서 수업을 들었다. 다만 세이임 선생님의 중급 강의는 항상 너무 이른 시간 (7시 30분이었던가…) 에 잡히고, 같이 듣는 친구가 상당히 멀리서 오기 때문에 11시에 시작하는 홍진형 선생님의 수업을 들었다.

그렇게 특별히 어느 선생님이 좋거나 나쁘다고 말할 수는 없다고 생각하는데, 개인적으로는 세이임 선생님의 수업을 수강할 때 받아 가지고 있던 템플릿이 입에 더 잘 익었기 때문에, 기본적으로 수업을 들을 때는 팁 같은 것을 주의깊게 듣고 템플릿은 세이임 선생님의 것을 가지고 연습했다. 저번에 학원을 다닐 때보다는 훨씬 열심히 공부했다고 생각한다.

스터디

굳이 해커스 어학원을 골랐던 이유는 세이임 선생님이 토익스피킹을 검색하면 가장 먼저 뜨는 소위 일타강사였기 때문이라는 이유도 있었지만, 해커스 어학원에서는 빡센 스터디 를 진행하고, 이 스터디가 상당히 효과적이라는 이야기를 들었었기 때문이다. 물론 잘 진행되기만 한다면!

나의 경우에는 우리 조 사람들이 대부분 영어를 어느 정도 하고, 피드백도 잘 해 주어서 엄청 도움이 됐다. 특히 스터디 조원들끼리 수업 후에 같이 과제를 하면서 2시간 이내에 빡세게 과제 및 피드백을 하고 나면, 그래도 집에 가면 할 과제가 많진 않구나 하는 심적 안도감도 같이 느꼈다. 실제로 집에서 추가적으로는 두세시간 정도밖에 과제를 하지 않았다.

단어장을 보고 인토네이션을 연습하는 과제가 있었는데, 학원에 가는 지하철이 비교적 한산했기 때문에 그 시간에 주로 연습했다. 단어를 보고 읽고 발음이 틀리면 세 번 정도 읽는 수준의 간단한 과제였기 때문에 그 정도로도 충분했다. 그리고 그렇게 입을 풀면서 가니, 수업 중에 집중하기도 좀 더 수월했던 것 같다.

시험

2월 두번째 주에 있는 토익스피킹 정규시험을 보았고, 결과는 150점에 lv.6 였다. 이것저것 아쉬웠던 점이 꽤 있기도 하고 애초 목표가 lv.7이었기 때문에 홍진형 선생님에게 첨삭을 받고 좀 더 공부하여 2월 네번째 주에 재시험을 보려고 했는데, 코로나 바이러스로 인해 설마설마 하다 결국 이틀 정도를 남기고 취소되었다.

시험을 보면서 느꼈던 것은, 물론! 점수를 잘 받기 위해 가장 중요한 것은 개인의 노력이지만, 나의 경우에는 자연스럽게, 천천히 말하는 연습을 많이 했다. 긴장해서 갑자기 기억이 나지 않는 템플릿에 집착하다 보면 빨리 말하게 되고, 그러다보니 웅얼거리듯 말하게 되면서 p/f 발음 같은 취약한 점들이 좀더 부각되곤 했다. 그래서 긴장할수록 최대한 쉬운 말들로 하려고 했다. 동어 반복을 하더라도 최대한 자연스럽게, 천천히!

물론, 100% 잘 됐다고는 하지 못하겠지만 그래도 저번보다 점수가 올랐고, lv.7의 바로 아래 점수라는 것에 약간의 만족을 느꼈다. 시험이 취소된 것은 아쉽지만, 시험이 재개된다면 다시 재시험에 응시하여 이번에는 꼭 lv.7을 넘길 예정이다. 이제부터는 2020 상반기 취준, 파이팅!