inblog logo
|
석우의 개발블로그
    함수형 프로그래밍

    함수형 프로그래밍 1주차

    섹션 1,2,3,4
    강석우's avatar
    강석우
    Sep 30, 2024
    함수형 프로그래밍 1주차
    Contents
    섹션 1. 함수형 자바스크립트 기본기평가와 일급일급 함수고차 함수섹션 2. ES6에서의 순회와 이터러블기존과 달라진 ES6에서의 리스트 순회Array, Set, Map을 통해 알아보는 이터러블/이터레이터 프로토콜이터러블/이터레이터 프로토콜전개 연산자섹션 3. 제너레이터와 이터레이터제너레이터와 이터레이터odds 제너레이터for...of, 전개 연산자, 구조 분해, 나머지 연산자섹션 4. map, filter, reducemap이터러블 프로토콜을 따른 map의 다형성filterreduce

    섹션 1. 함수형 자바스크립트 기본기

    평가와 일급

    평가란?

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

    일급이란?

    • 값으로 다룰 수 있는 것.

    • 변수에 담을 수 있는 것.

    • 함수의 인자로 사용될 수 있는 것.

    • 함수의 결과로 사용될 수 있는 것.

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

    일급 함수

    • 함수를 값으로 다룰 수 있다.

    • 조합성과 추상화의 도구

    const add5 = (a) => a + 5; // 함수를 값으로 다룰 수 있다.
    const f1 = () => () => 1; // 함수가 결과 값으로 사용될 수 있다.
    const f2 = f1();

    고차 함수

    • 함수를 값으로 다루는 함수

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

    const apply1 = (f) => f(1); // 함수를 인자 값으로 다루고 있다.
    const add2 = (a) => a + 2;
    console.log(apply1(add2));
    
    const times = (f, n) => {
      let i = -1;
      while (++i < n) f(i);
    };

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

    const addMaker = (a) => (b) => a + b; // a 를 계속 기억하고 있다.
    const add10 = addMaker(10);
    console.log(add10(5));
    console.log(add10(10));

    섹션 2. ES6에서의 순회와 이터러블

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

    ES5에서의 순회

    배열의 length를 받아 해당 길이만큼 순회 해준다.

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

    ES6에서의 순회

    for (const a of list) {
      console.log(a);
    }
    for (const a of str) {
      console.log(a);
    }

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

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

    • 이터러블 : 이터레이터를 리턴하는 [Symbol.iterator]()를 가진 값

    • 이터레이터 : {value, done} 객체를 리턴하는 next()를 가진 값

    • 이터러블/ 이터레이터 프로토콜 : 이터러블을 for…of, 전개 연산자 등과 함께 동작하도록 한 규약.

    Array를 통해 알아보기

    배열 내부에 Symbol.iterator 속성이 내장되어있다.
    for…of 문은 [Symbol.iterator]() 메소드를 호출하여 이터레이터 객체를 얻은 후, 순차적으로 next() 메소드를 호출하면서 하나씩 순회하는 것 이다.
    따라서 iterator를 null로 바꿔버리면 for…of문을 사용할 수 없다.

    const arr = [1, 2, 3];
    console.log(arr[Symbol.iterator]); // f values() { [native code] }
    for (const a of arr) console.log(a); // 1 2 3

    아래의 예시는 next() 메소드를 한번 호출하였기에 for…of 문에서 value 가 2부터 시작한다.

    const arr = [1, 2, 3];
    let iter1 = arr[Symbol.iterator]();
    console.log(iter1.next()); // {value: 1, done: false}
    for (const a of iter1) console.log(a); // 2 3

    Set을 통해 알아보기

    const set = new Set([1, 2, 3]);
     // index로 접근은 불가는 하나 for문을 통해 순회는 가능
     // for문을 통해 순회할 때는 Symbol.iterator를 통해 순회
    
    console.log(set[0], set[1], set[2]); // undefined undefined undefined
    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.keys()) console.log(a); // a b c
    for (const a of map.values()) console.log(a); // 1 2 3
    for (const a of map.entries()) console.log(a); // ['a', 1] ['b', 2] ['c', 3]

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

    - 이터러블: 이터레이터를 리턴하는 [Symbol.iterator]() 를 가진 값

    - 이터레이터: { value, done } 객체를 리턴하는 next() 를 가진 값

    - 이터러블/이터레이터 프로토콜: 이터러블을 for...of, 전개 연산자 등과 함께 동작하도록한 규약

    사용자 정의 이터러블을 통해 알아보기

    • Symbol.iterator는 next() 메서드를 갖고 있으며 value와 done 객체를 return 해준다.

    • Symbol.iterator는 자기 자신을 return 해야한다.

    const iterable = {
      [Symbol.iterator]() {
        let i = 3;
        return {
          next() {
            return i === 0 ? { done: true } : { value: i--, done: false };
          },
          [Symbol.iterator]() {
            return this;
          },
        };
      },
    };
    
    let iterator = iterable[Symbol.iterator]();
    iterator.next();
    for (a of iterator) console.log(a);
    console.log(iterator === iterator[Symbol.iterator]()); // true => Symbol.iterator는 자기 자신을 return하기 때문
    • 브라우저에서 사용되는 dom과 관련된 여러 값들도 이터레이터 프로토콜을 따르고 있다.

    for (const a of document.querySelectorAll("*")) log(a); 
    const all = document.querySelectorAll("*");
    let iter3 = all[Symbol.iterator]();
    log(iter3.next());
    log(iter3.next());
    log(iter3.next());

    전개 연산자

    const a = [1, 2];
    // a[Symbol.iterator] = null; 을 실행할 경우 iterable 에러가 발생한다.
    log([...a, ...arr, ...set, ...map.keys()]);

    섹션 3. 제너레이터와 이터레이터

    제너레이터와 이터레이터

    제너레이터란?

    • 이터레이터이자 이터러블을 생성하는 함수.

    • 제너레이터를 통해 어떤 값도 순회하게 만들 수 있음

    • 문장을 통해 순회할 수 있는 값을 생성한다.

    • return 값은 done 이 true가 되었을 때 반환하는 value 값이며 순환할 때 return 값은 포함되지 않는다.

    function* gen() {
      yield 1;
      yield 2;
      yield 3;
      return 100;
    }
    
    let iter = gen();
    console.log(iter[Symbol.iterator]() === iter); // true
    for (a of iter) console.log(a); // 1 2 3 ( return 값 포함되지 않음 )

    odds 제너레이터

    홀수만을 뽑아 출력해보자

    function* infinity(i = 0) {
      while (true) yield i++;
    }
    
    function* limit(l, iter) {
      for (const a of iter) {
        yield a;
        if (a === l) return;
      }
    }
    
    function* odds(l) {
      for (const a of limit(l, infinity(1))) {
        if (a % 2) yield a;
      }
    }
    
    let iter2 = odds(40);
    for (const a of iter2) console.log(a);

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

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

    섹션 4. map, filter, reduce

    map

    map 의 구성

    const products = [
      { name: "반팔티", price: 15000 },
      { name: "긴팔티", price: 20000 },
      { name: "핸드폰케이스", price: 15000 },
      { name: "후드티", price: 30000 },
      { name: "바지", price: 25000 },
    ];
    
    const map = (f, iter) => {
      let res = [];
      for (const a of iter) {
        res.push(f(a));
      }
      return res;
    };
    
    console.log(map((p) => p.name, products));

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

    • 위의 예시를 보면 querySelectorAll은 순회 가능할 것 같으나 querySelectorAll을 자세히 살펴보면 map 메서드가 없다.
      따라서 undefined를 내보내게 된다.

    document.querySelectorAll('*').map(el => el.nodeName); // Error
    console.log(document.querySelectorAll('*').map); // undefined
    • 하지만 document.querySelectorAll('*')는 이터러블 프로토콜을 따르고 있고, 위에서 생성한 map 함수는 이터러블 프로토콜을 따르는 for-of 구문을 사용하기 때문에 querySelectorAll을 순회할 수 있다.

    console.log(map((el) => el.nodeName, document.querySelectorAll("*"))); // ['HTML', 'HEAD', 'META', ...]
    • 마찬가지로 제너레이터 함수를 통해 만들어진 이터러블 또한 map을 돌릴 수 있다.

    function* gen() {
      yield 2;
      if (false) yield 3;
      yield 4;
    }
    
    log(map((a) => a * a, gen())); // [4, 16]

    filter

    filter의 구성

    const filter = (f, iter) => {
      let res = [];
      for (const a of iter) {
        if (f(a)) res.push(a);
      }
      return res;
    };
    console.log(...filter((p) => p.price < 20000, products));
    console.log(...filter((p) => p.price >= 20000, products));
    console.log(filter((n) => n % 2, [1, 2, 3, 4])); // [1, 3]

    • 제너레이터 함수를 통해 만들어진 이터러블 또한 filter에 적용할 수 있다.

    console.log(
      filter(
        (n) => n % 2,
        (function* () {
          yield 1;
          yield 2;
          yield 3;
          yield 4;
          yield 5;
        })()
      )
    ); // [1, 3, 5]

    reduce

    reduce의 구성

    const reduce = (f, acc, iter) => {
      if (!iter) {
        iter = acc[Symbol.iterator]();
        // 1번째 값으로 초기화
        acc = iter.next().value;
      }
      for (const a of iter) {
        acc = f(acc, a);
      }
      return acc;
    };

    Share article

    석우의 개발블로그

    RSS·Powered by Inblog