지연평가는 계산의 결과 값이 필요할 때까지 계산을 늦추는 기법으로 필요한 경우에 도달할 때까지 계산을 늦추기 때문에 불필요한 계산을 줄일 수 있다는 장점이 있다.
해당 부분을 학습하면서 take함수로 인해 지연평가가 주는 장점이 확 와닿았기 때문에 그 부분을 작성해보았다.
우선 take 함수의 코드를 확인하기전에 range함수를 먼저 알아보자.
range함수는 지정한 범위만큼 배열의 요소를 생성하는 함수로 테스트를 위해서 즉시평가하는 함수와 지연평가하는 함수 두가지 버전을 만들었다.
첫번째 range함수는 즉시평가하기 때문에 0~ 3까지 범위 값을 배열의 요소로 즉시 만든 후 동작하게 된다.
const range = (l) => {
let i = -1;
const res = [];
while(++i < l){
res.push(i);
}
return res;
}
const list1 = range(4); // [0, 1, 2, 3]
console.log(reduce(add, list1)); // 6
두번째 range함수는 지연평가를 하기 때문에 array를 만들지 않고 하나씩 값을 평가하여 동작하게 된다.
const L = {};
L.range = function *(l) {
let i = -1;
while(++i < l) {
yield i;
}
}
const list2 = L.range(4); // 제네레이터 함수를 실행했기 때문에 제네레이터 객체가 반환되었을 뿐 배열이 만들어지지 않았다.
console.log(list2); // 아직 평가가 안됨.
// iterator 내부에서 순회할때 마다 하나씩 평가 // list.next();
console.log(reduce(add, list2)); // 6
take함수는 지정한 갯수만큼 앞에서부터 뽑아내어 배열로 반환하는 함수이다.
// 지정한 갯수만큼 앞에서부터 뽑아내어 배열로 반환하는 함수
const take = (l, iter) => {
let res = [];
for(const a of iter) {
res.push(a);
if(res.length === l) return res;
}
return res;
}
이제 take함수와 range 함수를 조합하여 테스트를 해보면 다음과 같은 결과를 확인할 수 있는데 여기서 지연평가를 하는 함수는 실제로 순회할때마다 값을 하나씩 평가하기 때문에 범위가 커졌을 때 불필요한 계산을 줄임으로써 기존의 함수와 속도가 확연하게 차이나게 된 것이다.
// 1000000 범위의 크기를 가진 배열의 요소들 중에서
// 앞부분부터 시작하여 5개의 요소를 가져오는 함수를 10번 실행시키는 코드이다.
test('range', 10, () => take(5, range(1000000)));
test('L.range', 10, () => take(5, L.range(1000000)));
이전 포스팅에서 작성했던 go, map, filter, curry함수를 함께 사용하여 지연평가를 해보아도 얼마나 효율적으로 값을 평가하는지 확인할 수 있었다.
breakpoint를 적용하여 평가순서를 확인해보면 많은 도움이 된다.
// 즉시평가를 이용한 예시
go(range(10), // [0, 1, 2, 3, ...9]
map(n => n + 10), // [10, 11, 12, 13, ...19]
filter(n => n % 2), // [11, 13, 15, 17, 19]
take(2), // [11, 13]
console.log);
// 지연평가를 이용한 예시
go(L.range(10), // 0 1 2 3
L.map(n => n + 10), // 10 11 12 13
L.filter(n => n % 2), // 11 13
take(2), // 11 13 종료 (위에서 아래로 흐름)
console.log);