Stream
우리는 Stream 이라는 것으로 Generator 를 연결할 수 있다.
이것이 바로 자바8에 나오는 Stream의 개념이자 일반적으로 함수형 언어에서 Stream이라고 얘기하는 개념이다.
이때는 함수의 힘으로 구현하는데, 우리는 함수의 힘이 필요가 없다.
지연실행(Lazy Execution)을 제어문으로 구현할 수 있기 때문이다.
function 호출을 늦게 해서 지연시킬 필요가 없다.
Generator의 yield
가 있기 때문이다.
우리는 yield
를 활용하면 된다.
Generator 문만으로 지연실행을 일으켜서 Stream 을 연결할 수 있다.
const odd = function*(data) {
for (const v of data) {
console.log("odd", odd.cnt++);
// 나머지 구하는 식은 양의 정수만 된다.
// 따라서 절대값으로 계산한다.
if (Math.abs(v) % 2) yield v;
}
};
odd.cnt = 0;
const take = function*(data, n) {
for (const v of data) {
console.log("take", take.cnt++);
if (n--) yield v; else break;
}
};
take.cnt = 0;
for (const v of take(odd([1,2,3,4]), 2)) console.log(v);
방금 위에서 본 코드가 Stream 이다.
const Stream = class {
static get(v) {return new Stream(v);}
constructor(v) {
this.v = v;
this.filters = [];
}
add(gene, ...arg){
// v를 함수의 첫번째 인자값으로 전달 - 커링
// 제너레이터는 이터러블 객체를 반환할 수 있게 전달
this.filters.push(v => gene(v, ...arg));
return this;
}
*gene(){
let v = this.v;
for (const f of this.filters) v = f(v);
// v - 배열 갯수만큼 yield를 때림
yield* v;
}
}
const odd = function*(data) {
for (const v of data) if (v % 2) yield v;
};
const take = function*(data, n) {
for (const v of data) if (n--) yield v; else break;
};
// 아래 Stream.get은 별거 없다.
// 클래스 객체 생성할 때 new 때리기 싫어서 위에 보면 new Stream(v) 리턴한 것
// take 함수는 data와 n 인자값을 받는데, 아래 식에선 take 함수에 2라는 인자값 하나만 전달함.
// 그 이유는 위의 add 메소드에 v를 함수의 첫번째 인자값으로 전달하기 때문.
for (const v of Stream.get([1,2,3,4]).add(odd).add(take, 2).gene()) console.log(v);
// 1
// 3
위 코드 해석
- Stream.get([1,2,3,4]) 코드에 의해 new Stream 객체가 생성된다.
해당 객체는 {v = [1,2,3,4], filters = []} 이렇게 생겼다. - .add(odd) 를 통해 odd 함수를 add(gene, …arg)의 gene 인자로 전달한다.
this.filters.push(v => gene(v, …arg)) : filters 배열에 odd([1,2,3,4]) 를 넣는다. [odd([1,2,3,4])] 그리고 return this로 {v = [1,2,3,4], filters = [odd([1,2,3,4])]}을 반환한다.
- odd[1,2,3,4] -> yield 1 -> .add(take(1,2)) -> this.filters = take(1, 2); -> 2는 1이되고 -> yield 1
- odd[1,2,3,4] -> yield 3 -> .add(take(3, 1)) -> this.filters = take(3, 1); -> 1은 0이되고 -> yield 3
- 마지막 .gene() 을 통해 iterable 객체를 반환
- 콘솔에 1, 3이 찍힌다.
음.. 위처럼 복잡하게 생각하지 말자. 아래를 보자.
// 마지막 gene() 에 의해 아래와 같은 형태인 Stream 객체에서
// this.filters 부분을 for ... of 문으로 돌려
// take() 함수 실행되고 그 후에 odd() 함수 실행되면서..
// Stream.get([1,2,3,4]).add(odd).add(take, 2).gene() <- 이 순서대로...
// 1, 3 모두를 yield 하여 iterable 객체로 반환하는 거 같음
{
v : [1,2,3,4],
filters : [
odd(),
take(),
]
}
// 즉 위에서 1, 3 iterable 객체를 반환하고 최종적으로
// for (const v of [1, 3]) console.log(v); 가 실행되는 것 같다.
// 그래서 콘솔 창에 1, 3이 찍히는 것 같다.