간단한 버전의 range함수를 만들어 보자. 숫자하나를 받고 그 숫자하나의 크기만한 배열을 리턴하는 함수이다.
예를 들어서 아래만 구현해놓고 range를 사용한다면 [0, 1, 2, 3, 4]가 되는 배열을 리턴하고 밑에는 [0, 1] 배열을 리턴하는 함수를 만들어보자.
<script>
const range = _ => _;
log(range(5));
// [0, 1, 2, 3, 4]
log(range(2));
// [0, 1]
</script>
아래와 같이 동작하는 함수이다. range함수를 만들었는데
const range = l => {
let i = -1;
let res = [];
while (++i < l) {
res.push(i);
}
return res;
};
log(range(5));
// [0, 1, 2, 3, 4]
log(range(2));
// [0, 1]
range함수를 통해서 만들어진 배열에 있는 모든 값들을 더하는 코드를 작성해본다. add라는 함수를 만들고 list를 range로 만든다. 밑에 로그출력에 reduce을 넣고 reduce안에 add와 list를 넣는다. 이렇게 해서 확인하면 안에있는 모든 값을 더한다.
<script>
const add = (a, b) => a + b;
const range = l => {
let i = -1;
let res = [];
while (++i < l) {
res.push(i);
}
return res;
};
log(range(5));
// [0, 1, 2, 3, 4]
log(range(2));
// [0, 1]
var list = range(4);
log(reduce(add, list));
// 6
</script>
리스트를 확인해보면 아래와 같다.
var list = range(4);
log(list);
// [0, 1, 2, 3]
log(reduce(add, list));
// 6
이번에는 느긋한 range를 만들어보자. 위에코드를 가져와서 변경시켜본다. 일단 동일한 결과이지만 더 간단하게 변경시켜본다.
<script>
const L = {};
const range = l => {
let i = -1;
let res = [];
while (++i < l) {
res.push(i);
}
return res;
};
var list = L.range(4);
log(list);
// [0, 1, 2, 3]
log(reduce(add, list));
// 6
</script>
리턴값을 지우고 res도 지운다. push하는 것을 빼고 yield로 넣는다. 제너레이터 함수를 이용해서 이터레이터를 만드는 제너레이터 함수를 선언했고 함수는 i가 증가하는대로 yield가 되도록 했다.
동일한 결과가 나오는지 확인하면 결과는 동일하게 6으로 나온다.
두 코드에서의 차이가 있는데 log(list) 했을 때 차이가 있다. 위에서는 [0, 1, 2, 3] 배열이 출력되었고 이번에는 알기힘든 값이 출력되었다. 이것은 이터레이터로써 next를 통해 가져올 수 있는 것이다.
둘다 같은 결과를 만든 이유는 reduce라는 함수가 이터러블을 받기 때문이다. 이러터블을 받는다는 것은 range에서 쓰여진 list도 이터러블이고 느긋한 range에서 쓰여진 list라는 이터레이터도 이터러블이기 때문에 log(reduce(add, list))에서 list 이터러블을 이터레이터로 만든 후에 안에 값을 하나씩 조회하면서 결과를 만들기 때문에 reduce라는 함수가 같은 결과를 만들었다.
<script>
const L = {};
L.range = function* (l) {
let i = -1;
while (++i < l) {
yield i;
}
};
var list = L.range(4);
log(list);
// L.range {<suspended>}
log(reduce(add, list));
// 6
</script>
그냥 range랑 느긋한 range는 차이가 있는데 위에 range는 reduce에 list를 전달하기 전에 이미 range를 실행했을 때
( var list = range(4) ) list라는 변수에 담긴값이 배열인 상태이다. 즉 range를 실행했을 때 range(4)부분의 코드가 배열로 완전히 평가가 되었다는 것이다.
## range
var list = range(4);
log(list);
// 배열로 완전히 평가됨
L.range는 약간 다르다. 어떻게 다르냐? 여기까지 실행했을 때 L.range와 그냥 range 내부를 들여다보자.
var list = range(4);
log(list);
log를 찍으면서 log(i, "range")와 log(i, "L.range")를 찍어준다. 두 부분에서 var list = L.range(4)를 실행했을 때 어떻게 동작하는지 보자. 결과는 range라는 함수에서만 내부가 출력이 되었다.
L.range 느긋한 range는 함수의 어떤부분도 실행이 되지 않았다.
<script>
const add = (a, b) => a + b;
const range = l => {
let i = -1;
let res = [];
while (++i < l) {
log(i, 'range');
res.push(i);
}
return res;
};
var list = range(4);
// 0 "range"
// 1 "range"
// 2 "range"
// 3 "range"
</script>
## 느긋한 L.range
<script>
const L = {};
L.range = function* (l) {
let i = -1;
while (++i < l) {
log(i, 'L.range');
yield i;
}
};
var list = L.range(4);
</script>
아래와 같이 느긋한 range function에 log를 추가해도 결과가 나오지 않는 것을 확인할 수 있다.
<script>
const L = {};
L.range = function* (l) {
log('hi~~');
let i = -1;
while (++i < l) {
log(i, 'L.range');
yield i;
}
};
var list = L.range(4);
log(list);
</script>
그러면 L.range에서 언제 처음 이 부분들이 평가가 되는지 확인해보자.
log(i, 'L.range');
이터레이터의 안쪽에서 앞으로 값을 발생시킬 이터레이터가 이터레이터의 내부를 순회할 때 마다 하나씩 값이 평가가 된다. 그래서 next를 하기 전까지는 함수안에서(function) 어떤 코드도 동작하지 않기 때문에 내부에 값을 처음 순회할 때 결과가 꺼내진다.
var list = L.range(4);
log(list);
log(list.next().value)
//L.range {<suspended>}
//hi~~
//0 "L.range"
//0
한번 더하고 또하고 또 해본다. 구분하기 위해서 value를 지워놓고 출력해본다. 이런식으로 하나씩 찍힌다.
var list = L.range(4);
log(list);
log(list.next())
log(list.next())
log(list.next())
log(list.next())
그래서 L.range와 그냥 range는 이러한 차이가 있다. 그냥 range는 range를 실행했을 때 이미 모든 부분이 코드가 평가되면서 값이 만들어지고
L.range같은 경우에는 아래의 순서일 때는 아직 어떠한 코드도 평가가 되지 않는다.
var list = L.range(4);
log(list);
그런 상태에서 실제로 reduce 내부에 있는 값 즉 reduce에서 필요한 값,
예를 들어 아래코드를 보자. araray를 보면 필요한 값이라고는 볼 수가 없다.
실제로 순회해서 사용자에게 필요한 값을 만들어 낼 때까지는 array만으로는 아주 필요한 상태의 값은 아니라는 것이다.
즉 최종결과를 만들기 전까지는 배열형태가 아니여도 된다는 것이다.
var a = [0, 1, 2]
undefined
a
(3) [0, 1, 2]
a[0]
0
a[0] + a[1]
1
그래서 L.range 같은경우에는 배열형태가 아닌채로 평가가 완벽히 되지 않은 상태로 있다가
reduce안에서 해당하는 값이 필요할 때 까지 기다렸다가 평가가 이루어 져서 값을 꺼내도록 된다는 이야기이다.
<script>
const L = {};
L.range = function* (l) {
let i = -1;
while (++i < l) {
log(i, 'L.range');
yield i;
}
};
var list = L.range(4);
log(list);
log(reduce(add, list));
</script>
//L.range {<suspended>}
//1.html:29 0 "L.range"
//1.html:29 1 "L.range"
//1.html:29 2 "L.range"
//1.html:29 3 "L.range"
//1.html:36 6
결론은 range에서는 array를 다 만든다음에 그 다음에 배열로 전달이 되서 동작을 하게 되고
var list = range(4);
log(list);
log(reduce(add, list));
//(4) [0, 1, 2, 3]
// 6
L.range 느긋한 range 같은 경우는 array만들지 않고 하나씩 값을 꺼내기만 하는 것이다.
var list = L.range(4);
log(list);
log(reduce(add, list));
//0 "L.range"
//1 "L.range"
//2 "L.range"
//3 "L.range"
//6
사실 그냥 range같은 경우는 생략이 하나 더 되어있는 과정이 있는데 reduce안에 들어갔을 때 안쪽에서 iterator를 만든다.
const reduce = curry((f, acc, iter) => {
if (!iter) {
iter = acc[Symbol.iterator]();
acc = iter.next().value;
}
for of에서 이터러블이 이터레이터가 되도록 Symbol이터레이터가 된다고 배웠는데
const reduce = curry((f, acc, iter) => {
if (!iter) {
iter = acc[Symbol.iterator]();
acc = iter.next().value;
}
for (const a of iter[Symbol]) {
acc = f(acc, a);
}
return acc;
});
안쪽에서 이터레이터를 한번더 만드는 과정이 있는 것이다.
const reduce = curry((f, acc, iter) => {
if (!iter) {
iter = acc[Symbol.iterator]();
log(iter);
acc = iter.next().value;
}
for (const a of iter) {
acc = f(acc, a);
}
return acc;
});
그래서 그냥 range 같은 경우에는 array를 만들고 그 array를 이터레이터로 만들고 그 다음에 next를 하면서 순회를 하는 것이다.
0 "range"
1.html:12 1 "range"
1.html:12 2 "range"
1.html:12 3 "range"
1.html:19 (4) [0, 1, 2, 3]
Array Iterator {}
1.html:20 6
L.range 같은 경우는 실행되었을 때 이터레이터를 만들고 그 이터레이터가 자기자신을 그대로 리턴하는 이터러블이고 해당하는 함수를 실행하면 이미 만든 이터레이터를 리턴만하고 순회를 하기 때문에 좀 더 효율적이라고 볼 수 있다.
L.range {<suspended>}
L.range {<suspended>}
1.html:30 0 "L.range"
1.html:30 1 "L.range"
1.html:30 2 "L.range"
1.html:30 3 "L.range"
1.html:37 6
<출처 : 유인동 함수형 프로그래밍과 JavaScript ES6+>
https://www.inflearn.com/course/functional-es6/dashboard
함수형 프로그래밍과 JavaScript ES6+ - 인프런 | 강의
ES6+와 함수형 프로그래밍을 배울 수 있는 강의입니다. 이 강좌에서는 ES6+의 이터러블/이터레이터/제너레이터 프로토콜을 상세히 다루고 응용합니다. 이터러블을 기반으로한 함수형 프로그래밍,
www.inflearn.com
'JavaScript > 함수형 프로그래밍과 JavaScript ES6+' 카테고리의 다른 글
take 함수 (0) | 2021.08.20 |
---|---|
range와 느긋한 L.range 테스트 (0) | 2021.08.20 |
HTML로 출력하기 (0) | 2021.08.19 |
장바구니 예제(총 수량, 총 가격) (0) | 2021.08.19 |
함수 조합으로 함수 만들기 (0) | 2021.08.19 |