지연성 1

ddullgi·2022년 6월 16일
0
post-thumbnail

본 포스트는 인프런의 함수형 프로그래밍과 JavaScript ES6+ 강의(링크)를 듣고 정리한 내용입니다.


range와 느긋한 L.range


range

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

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

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

var list = range(4);

console.log(list); //(4) [0, 1, 2, 3]
console.log(reduce(add, list)); //6
  • range를 직접 만들어 보았다.

느긋한 L.range

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

var list = L.range(4);

console.log(list); //L.range {<suspended>}
console.log(reduce(add, list)); //6
  • 제너레이터로 구성된 L.range를 만들어 보았다.
    • 이터레이터를 반환하기 때문에 reduce 함수가 실행된다.

range와 느긋한 L.range의 차이

//range
const range = (l) => {
  let i = -1;
  let res = [];
  while (++i < l) {
    console.log(i, "range");
    res.push(i);
  }
  return res;
};
//0 'range'
//1 'range'
//2 'range'
//3 'range'

var list = range(4);

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

//L.range
const L = {};
L.range = function *(l) {
  let i = -1;
  while (++i < l) {
    console.log(i, "L.range");
    yield i;
  }
};
//출력 X

var list = L.range(4);

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

console.log(list.next());
//0 'L.range'
//{value: 0, done: false}
console.log(list.next());
//1 'L.range'
//{value: 1, done: false}
console.log(list.next());
//2 'L.range'
//{value: 2, done: false}
console.log(list.next());
//3 'L.range'
//{value: 3, done: false
  • range의 경우 list를 선언하자 마자 평가가 된다.
    • reduce의 인자로 들어갈 경우, 내부에서 이터레이터로 변환을 거친 후 순회 한다.
  • L.rangelist를 선언한 직후에 평가되지 않았다.
    • L.rangenext()를 호출 해야지만 한칸씩 순회한다.

function test(name, time, f) {
  console.time(name);
  while (time--) f();
  console.timeEnd(name);
}

test("L.range", 10, () => reduce, L.range(10000000));  //L.range: 0.013916015625 ms
test("range", 10, () => reduce, range(10000000));      //range: 0.070068359375 ms
  • L.range는 평가되지 않은 상태이기 때문에 reduce를 실행 할때 이터레이터로 변환을 거치지 않고 실행되기 때문에 위와 같은 좋은 효율성을 보인다.

take

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

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

console.log(take(5, L.range(100)));
//(5) [0, 1, 2, 3, 4]
  • take라는 함수를 만들어 봤다.
  • limit값을 받아 limit개의 iter인자를 리턴해준다.
  • 이터러블/ 이터레이터 프로토콜을 따른다.

console.time("");
console.log(take(5, range(10000000)));
console.timeEnd("");
//(5) [0, 1, 2, 3, 4]
//137.99609375 ms

console.time("");
console.log(take(5, L.range(10000000)));
console.timeEnd("");
//(5) [0, 1, 2, 3, 4]
//0.22607421875 ms
  • range는 10000000개의 배열을 만든 후, 그 배열을 이터레이터로 바꿔서 실행되므로 5개만 하는데도 오랜시간이 걸린다.
  • 반면, L.range는 5번의 next()만 실행하면 되기 때문에 엄청나게 시간이 적게 걸린다.


이터러블 중심 프로그래밍에서의 지연 평가(Lazy Evaluation)

  • 제때 계산법
    • 정말로 필요하기 전까지 평가를 최대한 미룸
    • 중복 연산을 피한다.
  • 느긋한 계산법
  • 제너레이터/이터레이터 프로토콜을 기반으로 구현

L : Lazy 지연성


L.map

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

var it = L.map((a) => a + 10, [1, 2, 3]);
console.log(it.next()); //{value: 11, done: false}
console.log(it.next()); //{value: 12, done: false}
console.log(it.next());	//{value: 13, done: false}
  • 지연성을 가진 map을 만들어주는 함수이다.
  • 평가를 미루는 성질을 가진다.
  • 평가 순서를 조작할 수 있는 준비가 되어있는 이터레이터를 반환하는 제너레이터 함수이다.

L.filter

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

var it = L.filter((a) => a % 2, [1, 2, 3, 4]);
console.log(it.next()); //{value: 1, done: false}
console.log(it.next()); //{value: 3, done: false}
console.log(it.next()); //{value: undefined, done: true}
  • filter 의 결과를 지연성을 가진 형태로 만들어주는 함수이다.
  • 평가를 미루는 성질을 가진다.
  • 평가 순서를 조작할 수 있는 준비가 되어있는 이터레이터를 반환하는 제너레이터 함수이다.

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

go(
  range(10),
  map((n) => n + 10),
  filter((n) => n % 2),
  take(2),
  console.log
);
//(2) [11, 13]
  • 기존의 함수들을 사용할경우 각 함수마다 평가를 완료하고 다음 함수로 넘어가는 가로방향 평가가 이루어 진다.
    • 길이가 길어질수록 효율성이 감소함

image



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

go(
  L.range(10),
  L.map((n) => n + 10),
  L.filter((n) => n % 2),
  take(2),
  console.log
);
//(2) [11, 13]
  • 지연 평가 된 함수를 사용할 경우 take -> L.filter -> L.map -> L.range -> L.map -> L.filter -> take 순으로 들어갔다 나오면서 1회의 iterator.next()에 대해서 모든 처리를 실행 한 후 다음 iterator.next()에 대해서 함수들의 처리를 실행 해준다.(이후 done: true가 될때까지 순회)
    • 세로 방향 평가
    • take처럼 모든 값을 순회 할 필요 없이 특정 조건을 만족 시켰을때 끝나는 함수와 사용할 때 시간적인 효울이 증가한다.

image



map, filter 계열 함수들이 가지는 결합 법칙

  • 사용하는 데이터가 무엇이든지

  • 사용하는 보조 함수가 순수 함수라면 무엇이든지

  • 아래와 같이 결합한다면 둘 다 결과가 같다.

    • [[mapping, mapping], [filtering, filtering], [mapping, mapping]]
    • =
    • [[mapping, filtering, mapping], [mapping, filtering, mapping]]
  • 세로 방향 평가 == 가로 방향 평가

profile
프론트엔드개발자를 꿈꾸는 예비 개발자

1개의 댓글

comment-user-thumbnail
2022년 11월 25일

ㅎㅇㅎㅇ

답글 달기