본문 바로가기

Web/유인동 함수형 프로그래밍 스터디

Iterable, iterator, generator 개념

결론

1.Iterable은 순환이 가능한 값이다.

2.Iterable은 Iterator를 리턴하는 [Symbol.interator]() 함수를 가진다. 

3.Iterator{ value:값, done: bool값 }형태 객체를 리턴하는 next()함수를 가진다.

4.Iterable은 for...of, 전개 연산자, 구조분해, 나머지 연산자 등과 함께 사용할 수 있다. ex) for...of는 next() done:true가 될 때까지 순환한다.

5.Generator는 Iterable이자 Iterable을 생성하는 함수이다. 함수명에 *를 붙여 선언한다. 

6. Generator에서 순회할 값 앞에 yield 키워드를 붙여준다. 그럼 이터레이터가 next()로 단계적 순회하듯, 제어가 가능하다. (for문 처럼 한 번에 와라락 순환하는게 아니라 요소 하나하나를 단계적으로 접근해서 순환한다. 예제 코드3 참조) 

7.Generator에서 리턴 값은 순회할 때 출력되지 않는다. 6번에서 말했든 yield 키워드가 붙은 것만 순회함.


예제 코드1

const array1 = [1,2,3,4,5,6];
let iter1 = array1[Symbol.iterator]();

console.log(iter1.next()); // {value:1, doen:false}
console.log(iter1.next()); // {value:2, doen:false}
console.log(iter1.next()); // {value:3, doen:false}
console.log(iter1.next()); // {value:4, doen:false}
console.log(iter1.next()); // {value:5, doen:false}
console.log(iter1.next()); // {value:6, doen:false}
console.log(iter1.next()); // {value:undefined, doen:true} -> array1에대한 순환이 끝남

예제1 출력을 보면 next()는 이전에 실행했던 지점을 기억해 두었다가 다음 호출엔 그 다음 값을 가져온다.

next()가 반환하는 객체에서 value 속성엔 이터러블 요소의 값이 담기고, done 속성에는 순환이 끝났는지 여부가 bool값으로 담긴다. 

즉, Iterator를 사용하면 순환/반복 처리를 단계별로 제어할 수 있다.

 

예제 코드2

function *gener(){	
    yield 'a';
    yield 'b';
    yield 'c';
    return 'The end!';
}

let iter2 = gener(); //iter2는 이터러블이자 이터레이터

console.log(iter2.next()); // {value:'a', doen:false}
console.log(iter2.next()); // {value:'b', doen:false}
console.log(iter2.next()); // {value:'c', doen:false}
console.log(iter2.next()); // {value:'The end', doen:true} -> 순환 끝

제너레이터 생성 및 출력 결과이다.


예제 코드3

//매표기기
function *machine(limit, makeTicket_iter){
    for(const ticket of makeTicket_iter){
        yield ticket
        if(ticket === limit) return;
    }
}
//티켓 생성기기
function *makeTicket(i = 0){
    while(true){
        yield i++;
    }
}
//발권 기계
function *ticketingMachine(l) {
    for(const a of machine(l, makeTicket(1))){
        yield a
        if(a === l ) return 'Sold Out'
    }
}

// 총 10개까지 티켓을 살 수 있는 기계
const ticketingMachine10 = ticketingMachine(10);

console.log(ticketingMachine10.next());
console.log(ticketingMachine10.next());
console.log(ticketingMachine10.next());
console.log(ticketingMachine10.next());
console.log(ticketingMachine10.next());
console.log(ticketingMachine10.next());
console.log(ticketingMachine10.next());
console.log(ticketingMachine10.next());
console.log(ticketingMachine10.next());
console.log(ticketingMachine10.next());
console.log(ticketingMachine10.next());
console.log(ticketingMachine10.next());

makeTicket 제너레이터의 경우, while문이 무한 루프이지만, yield 키워드를 사용함으로써 next()함수가를 통해 평가되기 전까진 다음 단계로 넘어가지 않기 때문에 일반적인 무한 루프문과는 달리 시스템에 부하를 주지 않고 무한대의 수를 만들어 낼 수 있다.  

 

 

 

 


예제 코드4

그 외 for...of, 구조분해, 전개연산자,나머지 연산자와도 함께 사용될 수 있다.

//매표기기
function *machine(limit, makeTicket_iter){
    for(const ticket of makeTicket_iter){
        yield ticket
        if(ticket === limit) return;
    }
}
//티켓 생성기기
function *makeTicket(i = 0){
    while(true){
        yield i++;
    }
}
// 홀수표 발권 기계
function *ticketingMachine(l) {
    for(const a of machine(l, makeTicket(1))){
        if(a % 2 ) yield a
    }
}

//15까지의 홀수표 발권기
const ticketingMachine15 = ticketingMachine(15);



//15까지의 홀수표를 펼쳐서 보여준다
console.log(...ticketingMachine(10)); // 1 2 3 4 5 6 7 8 9 10

const [first1, ...middle1] = ticketingMachine(15);
console.log(first1, middle1);

const [first2, middle2, ...rest] = ticketingMachine(15);
console.log(first2, middle2, rest);


이런 이터러블이 있다고 치자.

const products = [
{name:'검정후드', price:20000, quantity:1},
{name:'팔찌', price:7000, quantity:2},
{name:'지우개', price:1000, quantity:3},
{name:'폰케이스', price:5000, quantity:4},
{name:'썬글라스', price:18000, quantity:5},
{name:'키보드', price:28000, quantity:2},
];
const curry = f => (a, ..._) => _.length ? f(a, ..._) : (..._) => f(a, ..._);

const map = curry((f, iter) => {
    let res = [];
    for (const elem of iter) res.push(f(elem));	
    return res;
});

const filter = curry((f, values) => {
    let res = [];
    for(const elem of values) if(f(elem)) res.push(elem); 
    return res;
});

const reduce = curry((f, acc ,iter) => {
    if(!iter){
        iter = acc[Symbol.iterator]();
        acc = iter.next().value;
    }
    for(const elem of iter) {
        acc = f(acc,elem);
    }
    return acc;
});

const go = (...args) => {
    return reduce((a,f) => f(a), args);
}
const pipe = (f, ...fs) => (...as) => go(f(...as), ...fs);

const totalPrice = go(
    products,
    map(elem => elem.price),
    filter( elem => elem >= 20000),
    reduce((a,b) => a+b)
)

const f = pipe(
(a, b) => a + b,
a => a + 10,
a => a + 100);

const amount = pipe(
map(elem => elem.quantity),
reduce((a,b) => a+b));

console.log(amount(products));