TIL | 함수형 JS와 지연성

noopy·2021년 8월 11일
1

TIL

목록 보기
6/21

Week2. range, 느긋한 L.range, take, L.map 등 전반적인 지연성 정리

range, L.range, take, L.filter...

range

인자에 길이(len)를 넣으면, 0부터 해당 길이만큼의 숫자를 담은 배열을 리턴.

const range = len => {
  const result = [];
  let i = -1;
  while (++i < len) {
    result.push;
  }
  return result;
}

const add = (a, b) => a + b;

console.log(range(5)) // [0, 1, 2, 3, 4]
console.log(range(3)) // [0, 1, 2]
console.log(reduce(add, range(5))) // 10

reduce 순서: arr를 만들고 👉🏻 iterator를 만들고 👉🏻 그 다음 next()로 순회.

L.range

generator로 만든 range.

const L = {};
L.range = function *(len) {
  let i = -1;
  while (i < len) {
    yield i; // iterator를 만듦.
  }
}

console.log(L.range(5)) // L.range {<suspended>}
console.log(reduce(add, L.range(5))) // 10

L.range: arr를 만들지 않고, 실행될 때 하나씩 next()로 값을 꺼냄. 좀더 효율적임!

range와 L.range의 차이는, L.range는 미리 arr를 만들지 않고, 필요할 때 iterator.next()로 원하는 수만큼 꺼내기 때문에 지연성은 시간적으로 효율적.

take

많은 값을 받아서 잘라주는 함수.

const take = (limit, iterable) => {
  const result = [];
  
  for (const value of iterable) {
    result.push(value);
    if (result.length === limit) return result;
  }
  return result;
}

console.log(take(5 ,range(100))) // [0, 1, 2, 3, 4]
console.log(take(5 ,L.range(100))) // [0, 1, 2, 3, 4]

iterable을 받아 내부를 순회하며 일정 limit가 되면 잘라줌.
take함수는 iterable 프로토콜을 따르기 때문에 L.range와 같이 지연성을 갖는 함수를 iterator로 만들면, 사용할 수 있다는 장점. 👍
예를 들어 이런 것도 가능.

console.log(take(5, range(Infinity)))] // 브라우저가 뻗음.
console.log(take(5, L.range(Infinity)))] // [0, 1, 2, 3, 4]

find

조건에 맞는 첫번째 값 반환

const find = curry((func, iterable) => go(
  iterable,
  L.filter(func), // L.filter로 지연성을 주면 필요한 만큼만 순회가능.
  take(1),
  ([value]) => value
))

const users = [
  {age: 30},
  {age: 31},
  {age: 28},
  {age: 20},
  {age: 33},
];

console.log(find(age => age < 30, users)); // {{age: 28}}

join

const join = (sep =',', iterable) => 
  reduce((a, b) => `${a}${sep}${b}`, iterable);

const string = ['apple', 'juice'];
console.log(join('', string)) // 'applejuice'

지연평가 (게으른 평가? 영리한 평가?)

가장 필요한 때까지 평가를 미루다가 필요할 때 해당 코드들을 평가하며 값을 만듦. (연산을 효율적으로 줄일 수 있다!)

  • generator 기반으로, iterable 중심 프로그래밍의 지연평가 구현.
  • iterable 중심 프로그래밍 === collection 중심 프로그래밍 === list 중심 프로그래밍
    ex) map, filter, reduce, take, go... 기반 프로그래밍

앞서 구현했던 함수들을 지연성을 가진 함수로 다시 구현해보기.

L.map

L.map = function *(func, iterable) {
  for (const value of iterable) yield func(value);
};

let iterator = L.map(value => value * 2  ,[1, 2, 3]); // 이땐 평가되지 않음.
console.log(iterator.next()); // {value: 2, done: false};
console.log(iterator.next()); // {value: 4, done: false};
console.log(iterator.next()); // {value: 6, done: false};

L.filter

L.filter = function *(func, iterable) {
  for (const value of iterable) {
    if (func(value)) yield value;
  }
}

let iterator = L.map(value => value > 1  ,[1, 2, 3]); // 이땐 평가되지 않음.
console.log(iterator.next()); // {value: 2, done: false};
console.log([iterator.next().value]); // [3]

L.flatten

iterable을 받아 펼친 iterator를 반환.

const isIterable = (a) => a && a[Symbol.iterator]; // a가 있을 경우 a[Symbol.iterator] 확인

L.flatten = function *(iterable) {
  for (const a of iterable) {
    if (isIterable(a)) for (const b of a) yield b;
    else yield a;
  }
}

const arr = [[1, 2], 3, 4, [5, 6, 7]];
const iterator = L.flatten(arr);
console.log(iterator.next()) // {value: 1, done: false}
console.log(...iterator) // [1, 2, 3, 4, 5, 6, 7];

L.flatMap

L.flatten + L.map
이미 요소를 펼쳐주는 L.flatten을 구현했기 때문에, flatMap도 쉽게 구현 가능.
1. iterable을 mapping 함.
2. mapping 된 값을 flatten으로 펼쳐줌.
4. iterator를 리턴

L.flatMap = curry(pipe (
  L.map,
  L.flatten
));

지연평가된 함수와 일반 함수 비교

range, map, filter, take, reduce 중첩 사용

go(
  range(10), 
  map(num => num + 10),
  filter(num => num % 2),
  take(2),
  console.log); // [11, 13]

한 문단의 평가가 다 처리돼야 다음 문단을 평가함. 만약 range(무한) 이라면 브라우저가 range에서 뻗어서 다음으로 넘어가지 못할 것임.

L.range, L.map, L.filter, take, reduce 중첩 사용

go(
  L.range(10), 
  L.map(num => num + 10),
  L.filter(num => num % 2),
  take(2),
  console.log); // [11, 13]

작동방식이 특이함. 가로가 아닌 세로로 진행됨.

  1. range, map, filter에서 평가를 미루고 take(2)에서 첫 시작.
    take(2)에서 인자로 iterable을 받으려 하니,그 iterable은 filter에서 보낸 generator.
  2. filter의 generator에서 for of 문을 순회하려고 하니 그때의 generator는 map꺼임.
  3. map으로 올라가 순회하려고 하니 그때의 generator는 range꺼임.
  4. range에서 순회를 시작하고 yield 값을 map으로 보냄. map에서 mapping 후 yield 값을 filter로 보냄.
  5. filter의 조건이 만족되지 않으면 take로 보내지지 않고 다음 yield를 받기 위해 똑같은 과정을 거쳐 range로 올라감.
    .
    .
    .
    반복
  6. filter의 조건이 맞으면 take로 내려오고, take의 길이 조건이 맞으면 그대로 return.

느낀점

TIL 정리 방법을 어떻게 해야 효율적인지 고민인데, 강의를 수강하면서 자세하게 적으니까 하루가 훌쩍 지나간다. 따라서 앞으로는 TIL에선 딱 필요한 정의만 간단 요약하고, 좀 더 깊게 공부하고 싶을 때 article 항목으로 빼보려고 한다. 강의와 TIL로 하루를 보내기엔 아무래도 과제, 스터디 발표, 개인 JS 공부, SCSS 공부가 쌓여있어서 ㅋㅋㅋ 어떤 게 효율적이고 나에게 도움이 될 지 계속 고민하는 시간을 가지자.

profile
💪🏻 아는 걸 설명할 줄 아는 개발자 되기

0개의 댓글