숫자하나를 받고 그 숫자 이전까지 0부터 숫자가 늘어나는 수열의 배열을 리턴하는 함수
const range = l => {
let i = -1;
const result = [];
while (++i < l)
res.push(i);
return result;
}
range(4)
를 호출하면
와 같은 값이 나온다
const L = {};
L.range = function* (l) {
let i = -1;
while (++i < l)
yield i;
}
L.range(4)
를 호출하면
위와 같은 값이 나온다.
이는 generator를 생성한 iterator. 이전에 만든 map, filter, reduce는 이터러블 프로토콜을 따르기에 iterator를 인자로 주어도 정상적으로 작동한다.
일반 range()와 L.range()의 차이점은 range()는 호출하면 모든 값이 배열로 담겨 반환되지만, L.range()는 호출하여도 동작하지 않고 next메서드를 호출하는 그 시점 yield keyword가 있는 코드라인까지만 함수가 실행되고 다시 중단된다.
range()를 굳이 쓸 필요가 없는게 배열을 반환해도 순회하는 요소 한 개만 필요하지 그 배열 자체로 사용을 요하지 않는다. 그래서 L.range가 더 효율적인 형태인 것.
iterable의 총 length를 임의의 수만큼 잘라서 배열로 반환한다
const take = (l, iter) => {
const result = [];
for (const a of iter) {
result.push(a);
if (result.length === l ) return result;
};
}
이를 curry함수로 감싸주게 되면
go(
L.range(100),
take(3),
reduce((a, b) => a + b)
)
위와 같이 함수형 프로그래밍의 함수로 활용할 수 있게 된다.
L.map = function* (f, iter) {
for (const el of iter) yield f(el);
};
역시 평가하는 값만큼만 계산을 진행하게 됨
L.filter = function* (f, iter) {
for (const el of iter)
if (f(a)) yield el;
};
역시 평가하는 값만큼만 계산을 진행하게 됨
go(L.range(10), // 1. 일시중단 7. ~~
L.map(n => n + 10), //2. 일시중단 6. ~~
L.filter(n => n % 2), //3. 일시중단 5. next()까지가서 일시중단
take(2),//4. next()까지가서 일시중단
log);
디버거를 돌려보면 take부터 breakpoint가 잡힌다. 이유는 앞선 메서드들이 모두 제너레이터여서 모두 일시중단된 것. 제일 마지막에 next메서드를 사용하는 take의 중단점부터 역순으로 함수가 재개된다.
일반 함수가 함수마다 전체순회를 한 번 씩하는데 반해 수직적으로 순회가 일어나는 것.
[[mapping, mapping], [filtering, filtering], [mapping, mapping]]
===
[[mapping, filtering, mapping],[mapping, filtering, mapping]]
이터러블의 요소들을 하나의 문자열 값으로 반환한다.
const join = (seperator, iter) =>
reduce((a, b) => `${a}${seperator}${b}`);
Object.entries 메서드에 지연성을 더한 함수 [key, value]
형태의 요소를 가진 배열을 리턴한다.
L.entries = function* (obj) {
for (const key in obj)
yield [key, obj[key]];
}
조건에 일치하는 맨 첫 번째 값 한 개를 리턴하는 함수
const find = (f, iter) => go(
iter,
L.filter(f),
take(1),
([el]) => el
);
const map = curry(pipe(L.map, take(Infinity)));
const filter = curry(pipe(L.filter, take(Infinity)));
중첩된 iterable들을 모두 1개 뎁스의 Array로 리턴한다.
const isIterable = a => a && a[Symbol.iterator];
L.flatten = function* (iter) {
for (const a of iter) {
if (isIterable(a)) for (const b of a) yield b;
else yield a;
}
}
yield *
을 활용하면 위코드를 더 줄일 수 있다.
yield *iterable
은 for (const val of iterable) yield val;
과 같다.
3중, 4중의 iterable도 펼칠 수 있는 함수
L.deepFlat = function *f(iter) {
for (const of iter) {
if( isIterable(a)) yield *f(a);
else yield a;
}
}
flatten 후에 map 진행. map과 flatten을 따로따로 진행하지 않아서 더 효율적이다
const L.flatMap = pipe(L.map, L.flatten);