21. 8. 11(수) TIL(지연성 함수)

배준형·2021년 8월 11일
0

TIL

목록 보기
9/21
post-thumbnail

Javascript 지연성 함수

📌 지연성 함수

range 함수와 L.range 함수

range / L.range 함수 : 정수를 인자로 받아서 받은 정수 만큼의 인덱스를 배열에 담아 리턴하는 함수.

여기서 range는 함수가 실행됐을 때 즉시 배열을 만들어내는 함수이고, L.range는 이터러블 프로토콜을 따르면서 지연성을 갖고 해당 함수의 연산이 이루어지지 않다가 연산이 이루어질 때 값이 사용되는 함수로 정의한다.

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

console.log(range(4)); // [0, 1, 2, 3]


const L = {};
L.range = function *(l) {
  let i = -1;
  while (++i < l) yield i;
}

console.log(L.range(4)); // L.range {<suspended>}

※ 두 함수의 차이 : range는 실행했을 때 완전히 평가되는 반면, L.range는 generator를 통해 이터레이터를 반환하므로 .next()를 호출할 때 한 번씩 실행된다.

map, filter 함수와 L.map, L.filter 함수

위와 마찬가지로 기존 map, filter처럼 실행되는 순간 모든 평가가 이루어지는 함수와 동일한 역할을 수행하지만 호출될 때 하나씩 평가되고, 이터러블 프로토콜을 따르는 모든 객체를 활용할 수 있는 L.map, L.filter 함수를 만들 수 있다.

L.map = function *(f, iter) {
  for (const a of iter) yield f(a);
}

L.filter = function *(f, iter) {
  for (const a of iter) if (f(a)) yield a;
}

map, filter / L.map, L.filter의 차이 : 두 함수는 같은 결과적으로 같은 결과 값을 출력하지만, 내부적으로 실행되는 과정은 매우 다르다. 예를 들어,

// 10 ~ 19 까지의 숫자 중 앞에서부터 홀수 2개 만을 배열로 추출하는 것을 구현할 때

const take = curry((l, iter) => {
  let result = [];
  for (const a of iter) {
    result.push(a);
    if (res.length === l) return result;
  }
  return result;
});

// #1 map, filter 사용
go(range(10),
  map(n => n + 10),
  filter(n => n % 2),
  take(2)) // [11, 13]

// #2 L.map, L.filter 사용
go(L.range(10),
  L.map(n => n + 10),
  L.filter(n => n % 2),
  take(2)) // [11, 13]

1) map, filter 사용
순서대로 [ 0 ~ 9 배열 생성 ] → [ 모든 값에 + 10 ] → [ 모든 값 중 홀수만 리턴 ] → [ 앞에서 2개 추출 ] 과정을 거친다.

2) L.map, L.filter 사용
순서대로 [ take 함수가 실행되어 1개의 값을 찾는다 ] → [ filter에서 값을 찾는다 ] → [ map에서 값을 찾는다 ] → [ range함수에서 첫 수인 0을 계산한다 ] → [ map, filter, take 까지 순서대로 실행 후 11을 배열에 추가한다 ] → [ 이 과정을 반복한다 ]

얼핏보면 L.map, L.filter가 복잡해 보이지만 실제로는 지연성을 갖는 L.map, L.filter가 훨씬 빠르게 작용한다.(걸린 시간 #1: 5.45ms / #2: 0.177ms) 그 이유는 #1의 경우 모든 배열에 대해 평가하고 실행하게 되는 반면, #2의 경우 밑에서부터 필요한 만큼 값을 평가하고 사용하기 때문이다.

📌 지연 방식

지연 평가 또는 지연의 방식이 ES6 이전 자바스크립트에서는 지저분하거나 공식적으로 정의된 것이 아니라 약속을 만들어서 해당하는 라이브러리 안에서만 구동하는 지연 평가를 만들어야 했다면,

ES6 이후의 자바스크립트 에서는 공식적인 정의를 통해서 가능하게 되었고, 이러한 방식으로 구현된 지연성은 서로 다른 라이브러리, 서로 다른 사람들이 만든 함수들 모두 기본 값, 기본 객체에서 소통하기 때문에 조합성이 높고 어떠한 방식으로든 사용할 수 있다.

관련해서 map, filter는 지연성을 가질 수 있고, 해당 함수를 통해 인자로 받은 객체의 원소들에게 함수를 합성하는 역할을 한다면 reduce, take는 실제로 연산을 시작하는 함수라고 볼 수 있다. 따라서 지연성 함수를 통해서 연산이 필요한 순간에만 연산이 가능하도록 하여 성능을 향상시킬 수 있을뿐만 아니라 iterable protocol을 따르는 자바스크립트의 모든 객체에 사용이 가능하여 활용성이 높다.



📌 배운점

지금까지 자바스크립트를 학습하면서 기능을 위주로 공부했었다. 원하는 데이터를 구성, 추출하기 위해 다양한 기능들을 활용했고, 특히 코딩테스트에서 내가 풀었던 코드와 다른 사람이 푼 코드를 비교했을 때 많이 차이가 났었지만 중요하게 생각하지 않았다. 그러나 오늘 지연성 함수에 대해 학습하면서 많은걸 깨달았다.

대표적으로 같은 결과를 출력하더라도 성능은 매우 다를 수 있다는 것을 알게되었다. 같은 코딩테스트 문제에서도 다양한 풀이 방법이 있지만 고차함수만을 사용한 코드들은 눈여겨보지 않았고 어렵다고 생각했었는데, 학습하면 할수록 그런 방법들이 더 가독성이 좋고 수정도 편하다고 생각한다.

오늘 지연성이라는 것에 대해 배우면서 기존의 map, filter와 같은 함수들이 작동하는 원리에 대해 배우면서 iterable protocol을 따르는 이터레이터등이 성능에 어떤 영향을 끼치는지 알게 되었다.


📌 앞으로

새롭게 배운 점, 알게된 것들이 많이 있지만 역시 익숙하지는 않다. 오늘 배운 내용들도 오늘 하루만에 끝날 학습량이라고는 생각하지 않는다. 이론에서 그쳤지만 이를 이용해서 실제 코딩테스트 문제를 풀어보는 방식으로 학습하면 조금 더 이해하기가 수월할 것이라 생각한다. 그래서 기존에 풀었던 문제들도 위 방식들을 이용해 다시 풀어보면서 내용들을 학습해 나가야겠다.


profile
프론트엔드 개발자 배준형입니다.

0개의 댓글