즉시실행함수와 지연실행 함수 비교해보기

boyeonJ·2023년 7월 10일
1
post-thumbnail

** 공통으로 쓰이는 함수

 const reduce = curry((f, acc, iter) => {
    if (!iter) {
      iter = acc[Symbol.iterator]();
      acc = iter.next().value;
    } else {
      iter = iter[Symbol.iterator]();
    }
    let cur;
    while (!(cur = iter.next()).done) {
      const a = cur.value;
      acc = f(acc, a);
    }
    return acc;
  });


const go = (...args) => reduce((a,f)=>f(a), args);

const curry = f => (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);

1. range, map, filter, take, reduce 중첩 함수 사용

for of의 숨겨진 코드

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

  const map = curry((f, iter) => {
    let res = [];
    iter = iter[Symbol.iterator]();
    let cur;
    while (!(cur = iter.next()).done) {
      const a = cur.value;
      res.push(f(a));
    }
    return res;
  });

  const filter = curry((f, iter) => {
    let res = [];
    iter = iter[Symbol.iterator]();
    let cur;
    while (!(cur = iter.next()).done) {
      const a = cur.value;
      if (f(a)) res.push(a);
    }
    return res;
  });

  const take = curry((l, iter) => {
    let res = [];
    iter = iter[Symbol.iterator]();
    let cur;
    while (!(cur = iter.next()).done) {
      const a = cur.value;
      res.push(a);
      if (res.length == l) return res;
    }
    return res;
  });

go(range(100000), map(n => n + 10), filter(n => n % 2), take(10), log);


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

const map = curry((f, iter)=>{
	iter = iter[Symbol.lterator]();
  	let cur;
  	let res = [];
  	while(!(cur=iter.next()).done){
    	const a = cur.value;
      	res.push(f(a));
    }
  	return res;
});

const filter = curry((f,iter)=>{
	iter = iter[Symbol.lterator]();
  	let cur;
  	let res = [];
  	while(!(cur=iter.next()).done){
    	const a = cur.value;
      	if(a) res.push(a);
    }
  	return res;
});

const take = curry((l, iter)=>{
iter = iter[Symbol.lterator]();
  	let cur;
  	let res = [];
  	while(!(cur=iter.next()).done){
    	const a = cur.value;
      	res.push(a);
      	if(res.length===l) return res;
    }
  	return res;
});

let L = {};

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

L.map = curry(funciton * (f, iter)=>{
	iter = iter[Symbol.lterator]();
  	let cur;
  	while(!(cur=iter.next()).done){
    	const a = cur.value;
      	yield f(a);
    }
});


L.map = curry(funciton * (f, iter)=>{
	iter = iter[Symbol.lterator]();
  	let cur;
  	while(!(cur=iter.next()).done){
    	const a = cur.value;
      	if(a) yield a;
    }
});

그에 반해서 지연실행 함수에서는 

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

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

  L.map = curry(function* (f, iter) {
    iter = iter[Symbol.iterator]();
    let cur;
    while (!(cur = iter.next()).done) {
      const a = cur.value;
      yield f(a);
    }
  });

  L.filter = curry(function* (f, iter) {
    iter = iter[Symbol.iterator]();
    let cur;
    while (!(cur = iter.next()).done) {
      const a = cur.value;
      if (f(a)) {
        yield a;
      }
    }
  });

go(L.range(Infinity), L.map(n => n + 10), L.filter(n => n % 2), take(10), log);

코드를 실행하면 가장 먼저 take에 들어온다. 그 이유는 위에서부터 차례대로 제너레이터가 만든 이터레이터를 반환하기 때문이다. 이터네리이터는 바로 평가 되지 않고 지연 실행 되기 때문에 take부터 실행됨.

그래서 지연실행의 실행순서가 다르다.

지연실행 함수 = 제너레이터

지연 평가 : 메모리 효율성, 시간 효율성
다형성과 조합성


둘의 차이점!

우선 1번 즉시 실행되는 함수의 경우 한번에 배열을 다 만들고 하나씩 map, filter를 실행한후 take로 배열을 자른다.

그리고 2번의 경우 아래의 순서로 지연 실행됩니다.
1. range가 제너레이터가 만든 이터레이터를 map에게 전달
2. map이 제너레이터가 만든 이터레이터를 filter에게 전달
3. filter는 제너레이터가 만든 이터레이터를 take에게 전달

  • 여기서 이 이터레이터는 well-formed iterator이기 때문에 Symbol.iterator 매소드를 가집니다. 따라서 해당 코드는 아래 두가지 모두 가능하도록 다형성이 보장된 함수입니다.
  1. 이터러블 객체(=배열)
  2. 이터레이터(=제너레이터가 만든)
  1. take에서 iter.next()를 하게 되면 filter의 이터레이터가 평가되면서 실행된다.
  2. filter 내부에서 next를 하게 되면 map의 이터레이터가 평가되어 실행되고,
  3. map내부에서 next를 하게 되면 range의 이터레이터가 평가되어 실행됩니다.
  4. 그래서 평가된 값 1이 반환되고 해당 값이 map의 a로 들어온다.
  5. 그 값이 filter의 a로 결국, take의 a까지 들어온다.

지연 실행 함수의 장점

이렇든 즉시 평가 되는 함수와는 달리 제너레이터로 지연실행 시킨 함수는 아래와 같은 장점을 가지고 있습니다.

  1. 메모리 효율성 : 제너레이터 함수는 이터레이터를 통해 한 번에 하나의 값을 생성하므로, 큰 데이터 세트를 한꺼번에 처리하지 않고 필요한 만큼 생성할 수 있습니다. 이는 메모리 사용을 효율적으로 관리할 수 있게 해줍니다.
  2. 지연 평가: 제너레이터 함수는 값을 필요로 할 때까지 실행을 유보할 수 있습니다. 제너레이터는 이터레이터 프로토콜을 따르므로, next() 메서드를 호출할 때마다 값을 생성하고 반환합니다. 이를 통해 값을 필요로 하는 시점까지 실행을 연기할 수 있으며, 필요에 따라 중간 결과를 반환하고 이를 다른 함수에 전달할 수 있습니다.
  3. 다형성과 조합성: 제너레이터 함수는 이터러블 객체와 이터레이터 모두를 처리할 수 있는 다형성을 가지고 있습니다. 이는 제너레이터 함수가 다른 제너레이터 함수와 함께 연결되어 복잡한 데이터 처리 파이프라인을 구성할 수 있음을 의미합니다. 여러 제너레이터 함수를 조합하고 연결함으로써 데이터를 변형하거나 걸러내는 등 다양한 작업을 수행할 수 있습니다.

그 중에서도 지연 평가의 장점은..?
1. 메모리 효율성 : 한꺼번에 처리하지 않고 필요한 만큼 생성하므로, 메모리 사용을 효율적으로 관리
2. 시간 효율성 : 복잡한 데이터 처리 작업을 수행할 때 중간 결과를 효율적으로 활용하고, 필요한 시점에서만 계산을 수행하여 시간을 절약

0개의 댓글