함수형 프로그래밍 1주차

섹션 1,2,3,4
강석우's avatar
Sep 30, 2024
함수형 프로그래밍 1주차

섹션 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

석우의 개발블로그