본문으둜 κ±΄λ„ˆλ›°κΈ°

🐀 Chapter 6: 반볡기 μ΄ν•΄ν•˜κΈ°

πŸ¦„ 반볡기 μ΄ν•΄ν•˜κΈ°β€‹

πŸ“š λ°˜λ³΅κΈ°μ™€ 반볡기 μ œκ³΅μžβ€‹

  • 반볡기(iterator)λŠ” λ‹€μŒκ³Ό 같은 νŠΉμ§•μ΄ μžˆλŠ” 객체이닀.
  1. nextλΌλŠ” μ΄λ¦„μ˜ λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•œλ‹€.
  2. next λ©”μ„œλ“œλŠ” value와 doneμ΄λΌλŠ” 두 개의 속성을 가진 객체λ₯Ό λ°˜ν™˜ν•œλ‹€.
  • λ‹€μŒ μ½”λ“œμ—μ„œ createRangeIterable ν•¨μˆ˜λŠ” next λ©”μ„œλ“œκ°€ μžˆλŠ” 객체λ₯Ό λ°˜ν™˜ν•˜λ―€λ‘œ 이 ν•¨μˆ˜λŠ” 반볡기λ₯Ό μ œκ³΅ν•˜λŠ” 역할을 ν•œλ‹€. 이처럼 반볡기λ₯Ό μ œκ³΅ν•˜λŠ” ν•¨μˆ˜λ₯Ό 반볡기 제곡자(iterable)라고 ν•œλ‹€.
export const createRangeIterable = (from: number, to: number) => {
let currentValue = from;

return {
next() {
const value = currentValue < to ? currentValue++ : undefined;
const done = value === undefined;

return { value, done };
}
}
}
  • λ‹€μŒμ€ createRangeIterable ν•¨μˆ˜κ°€ μ œκ³΅ν•˜λŠ” 반볡기λ₯Ό μ‚¬μš©ν•˜λŠ” μ˜ˆμ΄λ‹€.
import { createRangeIterable } from './createRangeIterable';

const iterator = createRangeIterable(1, 3 + 1); // λ°˜λ³΅κΈ°λŠ” ν˜„μž¬ λ™μž‘ν•˜μ§€ μ•ŠλŠ”λ‹€.

while (true) {
const { value, done } = iterator.next();

if (done) {
break;
}

console.log(value);
}
  • createRangeIterable ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•΄ 반볡기λ₯Ό μ–»κ³  iterator λ³€μˆ˜μ— μ €μž₯ν•˜κ³  λ°˜λ³΅κΈ°λŠ” 이처럼 반볡기 제곡자λ₯Ό ν˜ΈμΆœν•΄μ•Όλ§Œ 얻을 수 μžˆλ‹€.

πŸ“š λ°˜λ³΅κΈ°λŠ” μ™œ ν•„μš”ν•œκ°€?​

  • 반볡기 μ œκ³΅μžλŠ” μ–΄λ–€ λ²”μœ„μ˜ 값을 ν•œκΊΌλ²ˆμ— μƒμ„±ν•΄μ„œ 배열에 담지 μ•Šκ³  값이 ν•„μš”ν•  λ•Œλ§Œ μƒμ„±ν•œλ‹€.
  • λ‹€μŒμ€ 5μž₯의 range ν•¨μˆ˜μ΄λ‹€.
export const range = (from, to) => from < to ? [from, ...range(from + 1, to)] : [];
  • createRangeIterable ν•¨μˆ˜λŠ” 값이 ν•„μš”ν•œ μ‹œμ μ— λΉ„λ‘œμ†Œ μƒμ„±ν•˜μ§€λ§Œ, range ν•¨μˆ˜λŠ” 값이 ν•„μš”ν•œ μ‹œμ λ³΄λ‹€ 이전에 미리 μƒμ„±ν•œλ‹€. λ”°λΌμ„œ μ‹œμŠ€ν…œ λ©”λͺ¨λ¦¬μ˜ νš¨μœ¨μ„±μ΄λΌλŠ” κ³Όμ μ—μ„œ 보면 createRangeIterable ν•¨μˆ˜κ°€ λ©”λͺ¨λ¦¬λ₯Ό 훨씬 적게 μ†Œλͺ¨ν•œλ‹€.

πŸ“š for...of ꡬ문과 [Symbol.iterator] λ©”μ„œλ“œβ€‹

  • 5μž₯의 range ν•¨μˆ˜λŠ” for...of ꡬ문의 of뒀에 올 수 μžˆλ‹€.
for (let value of range(1, 3 + 1)) {
console.log(value); // 1 2 3
}
  • κ·ΈλŸ¬λ‚˜ λ‹€μŒ μ½”λ“œμ²˜λŸΌ μ•žμ—μ„œ μž‘μ„±ν•œ createRangeIterable ν•¨μˆ˜λ₯Ό for...of ꡬ문에 μ μš©ν•˜λ©΄ [Symbol.iterator]() λ©”μ„œλ“œκ°€ μ—†λ‹€λŠ” 였λ₯˜κ°€ λ°œμƒν•œλ‹€.
const iterable = createRangeIterable(1, 3 + 1);
for(let value of iterable) {
console.log(value);
}

6-1

  • 이 였λ₯˜λŠ” createRangeIterable ν•¨μˆ˜λ₯Ό λ‹€μŒ RangeIterable처럼 클래슀둜 κ΅¬ν˜„ν•΄μ•Ό ν•œλ‹€λŠ” 것을 μ˜λ―Έν•œλ‹€.
  • RangeIterable ν΄λž˜μŠ€λŠ” [Symbol.iterator] λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•˜κ³  μžˆλ‹€.
class RangeIterable {
constructor(public from: number, public to: number) {};

[Symbol.iterator]() {
const that = this;
let currentValue = that.from;

return {
next() {
const value = currentValue < that.to ? currentValue++ : undefined;
const done = value === undefined;

return { value, done };
}
}
}
}

const iterator = new RangeIterable(1, 3 + 1);

for (const value of iterator) {
console.log(value); // 1 2 3
}

πŸ“š Iterable<T>와 Iterator<T> μΈν„°νŽ˜μ΄μŠ€β€‹

  • νƒ€μž…μŠ€ν¬λ¦½νŠΈλŠ” 반볡기 μ œκ³΅μžμ— Iterable<T>와 Iterator<T> μ œλ„€λ¦­ μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€. Iterable<T>λŠ” λ‹€μŒμ²˜λŸΌ μžμ‹ μ„ κ΅¬ν˜„ν•˜λŠ” ν΄λž˜μŠ€κ°€ [Symbol.iterator] λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•œλ‹€λŠ” 것을 λͺ…ν™•ν•˜κ²Œ μ•Œλ €μ£ΌλŠ” 역할을 ν•œλ‹€.
class κ΅¬ν˜„ν΄λž˜μŠ€ implements Iterable<생성할_κ°’μ˜_νƒ€μž…> {}
  • Iterator<T>λŠ” λ°˜λ³΅κΈ°κ°€ 생성할 κ°’μ˜ νƒ€μž…μ„ λͺ…ν™•ν•˜κ²Œ ν•΄μ€€λ‹€.
[Symbol.iterator](): Iterator<생성할_값을_νƒ€μž…> {}
  • λ‹€μŒμ€ 반볡기 제곡자λ₯Ό νƒ€μž…μŠ€ν¬λ¦½νŠΈκ°€ μ œκ³΅ν•˜λŠ” Iterable<T>와 Iterator<T>λ₯Ό μ‚¬μš©ν•΄ κ΅¬ν˜„ν•œ μ˜ˆμ΄λ‹€.
export class StringIterable implements Iterable<string> {
constructor(private strings: string[] = [], private currentIndex: number = 0) {}

[Symbol.iterator](): Iterator<string> {
const that = this;
let currentIndex = that.currentIndex;
let length = that.strings.length;

const iterator: Iterator<string> = {
next(): { value: string, done: boolean } {
const value = currentIndex < length ? that.strings[currentIndex] : undefined;
const done = value === undefined;

return { value, done };
}
}

return iterator;
}
}

for (let value of new StringIterable(['hello', 'world', '!'])) {
console.log(value); // hello world !
}

πŸ¦„ 생성기 μ΄ν•΄ν•˜κΈ°β€‹

  • yieldλŠ” 마치 return ν‚€μ›Œλ“œμ²˜λŸΌ 값을 λ°˜ν™˜ν•œλ‹€. yieldλŠ” λ°˜λ“œμ‹œ function* ν‚€μ›Œλ“œλ₯Ό μ‚¬μš©ν•œ ν•¨μˆ˜μ—μ„œλ§Œ ν˜ΈμΆœν•  수 μžˆλ‹€. μ—¬κΈ°μ„œ function* ν‚€μ›Œλ“œλ‘œ λ§Œλ“  ν•¨μˆ˜λ₯Ό 생성기(generator)라고 λΆ€λ₯Έλ‹€.
export function* generator() {
console.log('start');
let value = 1;

while (value < 4) {
yield value++;
}

console.log('finish');
};

for (const value of generator()) {
console.log(value);
// start
// 1
// 2
// 3
// finish
}

πŸ“š setInterval ν•¨μˆ˜μ™€ μƒμ„±κΈ°μ˜ μœ μ‚¬μ„±β€‹

  • 생성기가 λ™μž‘ν•˜λŠ” 방식을 세미코루틴(semi-coroutine)이라고 ν•œλ‹€.
  • 세미코루틴은 νƒ€μž…μŠ€ν¬λ¦½νŠΈμ²˜λŸΌ 단일 μŠ€λ ˆλ“œλ‘œ λ™μž‘ν•˜λŠ” ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄κ°€ 마치 닀쀑 μŠ€λ ˆλ“œλ‘œ λ™μž‘ν•˜λŠ” κ²ƒμ²˜λŸΌ 보이게 ν•˜λŠ” κΈ°λŠ₯을 ν•œλ‹€.
  • setInterval ν•¨μˆ˜λŠ” λ¬΄ν•œνžˆ λ°˜λ³΅ν•˜μ§€λ§Œ clearInterval ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜λ©΄ 멈좜 수 μžˆλ‹€.
const period = 1000;
let count = 0;

console.log('program started...');
const id = setInterval(() => {
if (count >= 3) {
clearInterval(id);
console.log('program finished');
} else {
console.log(++count);
}
}, period);

// program started...
// 1
// 2
// 3
// program finished...
  • setInterval을 λ™μž‘μ‹œν‚¨ 뢀뢄이 메인 μŠ€λ ˆλ“œ, setInterval의 콜백 ν•¨μˆ˜λŠ” μž‘μ—… μŠ€λ ˆλ“œλ₯Ό λ– μ˜¬λ¦¬κ²Œ ν•˜λ‚Ÿ. μƒμ„±κΈ°λŠ” 이처럼 일반적인 νƒ€μž…μŠ€ν¬λ¦½νŠΈ μ½”λ“œμ™€λŠ” μ’€ λ‹€λ₯Έ λ°©μ‹μœΌλ‘œ λ™μž‘ν•œλ‹€.
  • 코루틴은 μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 레벨의 μŠ€λ ˆλ“œμ΄λ‹€. μ½”λ£¨ν‹΄μ˜ λͺ©μ μ€ μš΄μ˜μ²΄μ œμ— 뢀담을 주지 μ•ŠμœΌλ©΄μ„œ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ μŠ€λ ˆλ“œλ₯Ό 마음껏 μ“Έ 수 있게 ν•˜λŠ” 것이닀. 그런데 코루틴은 μŠ€λ ˆλ“œμ΄λ―€λ‘œ 일정 주기에 따라 μžλ™μœΌλ‘œ λ°˜λ³΅ν•΄μ„œ μ‹€ν–‰λœλ‹€.
  • λ°˜λ©΄μ— μƒμ„±κΈ°λŠ” 절반만 코루틴이닀. 즉, λ°˜λ³΅ν•΄μ„œ μ‹€ν–‰ν•  수 μžˆμ§€λ§Œ μžλ™μœΌλ‘œ μ‹€ν–‰λ˜μ§€ λͺ»ν•˜λŠ” 코루틴이닀. 생성기가 λ§Œλ“€μ–΄μ€€ 반볡자의 next 호좜 λ•Œ ν•œ 번 μ‹€ν–‰λ˜κ³  κ³§λ°”λ‘œ λ©ˆμΆ˜λ‹€. 이처럼 μƒμ„±κΈ°λŠ” μžλ™μœΌλ‘œ 반볡 μ‹€ν–‰λ˜μ§€ μ•ŠμœΌλ―€λ‘œ 세미코루틴이라고 ν•œλ‹€.

πŸ“š function* ν‚€μ›Œλ“œβ€‹

  • generator ν•¨μˆ˜μ™€ 일반 ν•¨μˆ˜μ™€ λ‹€λ₯Έμ 
  1. function* ν‚€μ›Œλ“œλ‘œ ν•¨μˆ˜λ₯Ό μ„ μ–Έν•œλ‹€.
  2. ν•¨μˆ˜ λͺΈν†΅ μ•ˆμ— yield 문이 μžˆλ‹€.
  • 즉, function* ν‚€μ›Œλ“œλ‘œ μ„ μ–Έλœ ν•¨μˆ˜κ°€ 생성기인데, μƒμ„±κΈ°λŠ” 였직 function* ν‚€μ›Œλ“œλ‘œ μ„ μ–Έν•΄μ•Ό ν•˜λ―€λ‘œ ν™”μ‚΄ν‘œ ν•¨μˆ˜λ‘œλŠ” 생성기λ₯Ό λ§Œλ“€ 수 μ—…μ‚³.

πŸ“š yield ν‚€μ›Œλ“œβ€‹

  • 생성기 ν•¨μˆ˜ μ•ˆμ—λŠ” yield 문을 μ‚¬μš©ν•  수 μžˆλ‹€. yieldλŠ” μ—°μ‚°μž ν˜•νƒœλ‘œ λ™μž‘ν•˜λ©° λ‹€μŒμ²˜λŸΌ 두 가지 κΈ°λŠ₯을 ν•œλ‹€.
  1. 반볡기λ₯Ό μžλ™μœΌλ‘œ λ§Œλ“€μ–΄ μ€€λ‹€.
  2. 반볡기 제곡자 역할도 μˆ˜ν–‰ν•œλ‹€.
export function* rangeGenerator(from: number, to: number) {
let value = from;

while(value < to) {
yield value++;
}
}

let iterator = rangeGenerator(1, 3 + 1);

while (1) {
const { value, done } = iterator.next();

if (done) {
break;
}

console.log(value); // 1 2 3
}

for (const value of rangeGenerator(4, 6 + 1)) {
console.log(value); // 4 5 6
}

πŸ“š 반볡기 제곡자의 λ©”μ„œλ“œλ‘œ λ™μž‘ν•˜λŠ” 생성기 κ΅¬ν˜„β€‹

  • StringIterable 클래슀λ₯Ό 생성기λ₯Ό μ‚¬μš©ν•˜λ©΄ κ°„κ²°ν•˜κ²Œ κ΅¬ν˜„ν•  수 μžˆλ‹€.
export class IterableUsingGenerator implements Iterable<string> {
constructor(private values: string[] = [], private currentIndex: number = 0) {}

[Symbol.iterator] = function* () {
while(this.currentIndex < this.values.length) {
yield this.values[this.currentIndex++];
}
}
}

for (const item of new IterableUsingGenerator([1, 2, 3])) {
console.log(item);
}

for (const item of new IterableUsingGenerator(['hello', 'world', '!'])) {
console.log(item);
}

πŸ“š yield* ν‚€μ›Œλ“œβ€‹

  • yield*λŠ” λ‹€λ₯Έ μƒμ„±κΈ°λ‚˜ 배열을 λŒ€μƒμœΌλ‘œ λ™μž‘ν•œλ‹€.
function* gen12() {
yield 1;
yield 2;
}

export function* gen12345() {
yield* gen12();
yield* [3, 4];
yield 5;
}

πŸ“š yield λ°˜ν™˜κ°’β€‹

  • yield μ—°μ‚°μžλŠ” 값을 λ°˜ν™˜ν•œλ‹€.
export function* gen() {
let count = 5;
let select = 0;

while(count--) {
select = yield `you select ${select}`;
}
}

export const random = (max, min = 0) => Math.round(Math.random() * (max - min)) + min;
  • yield μ—°μ‚°μžμ˜ λ°˜ν™˜κ°’μ€ 반볡기의 next λ©”μ„œλ“œ 호좜 λ•Œ λ§€κ°œλ³€μˆ˜μ— μ „λ‹¬ν•˜λŠ” 값이닀.
  • next λ©”μ„œλ“œ 호좜 λ•Œ λ‚œμˆ˜λ₯Ό 생성해 μ „λ‹¬ν•œλ‹€.
const iter = gen();

while(true) {
const { value, done } = iter.next(random(10, 1));

if(done) {
break;
}

console.log(value);
// you select 0
// you select 3
// you select 9
// you select 2
// you select 9
}
  • μ‹€ν–‰ κ²°κ³ΌλŠ” 이전에 next λ©”μ„œλ“œκ°€ μ „λ‹¬ν•œ 값이 λ‹€μ‹œ gen ν•¨μˆ˜μ˜ λ‚΄λΆ€ λ‘œμ§μ— μ˜ν•΄ ν˜„μž¬μ˜ value 값이 λ˜μ–΄ 좜λ ₯λœλ‹€.