| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- Hash
- Dijkstra
- DP
- Two Points
- SQL
- union find
- 스토어드 프로시저
- Brute Force
- Stored Procedure
- MYSQL
- 그래프
- 다익스트라
- Trie
- binary search
- two pointer
- 이진탐색
- String
- Today
- Total
codingfarm
3. 자바스크립트의 이터레이션 프로토콜 본문
1.소개
JavaScript는 Iterator를 반환하는 아래와 같은 메서드를 가지고 있을 경우 Iterable로 규정한다.
이를 iteration protocol라 칭한다.
[Symbol.iterator]() { return {next() {...}}; }대표적인 Iterable은 Array, Map, Set 등이 있으며,
Iterator를 통해 for...of 문, 전개연산자, 구조 분해 등 다양한 기능들을 제공한다.
2. Iterator
특정 크기의 자연수 지연 생성하는 Iterator를 반환하는 일반함수를 구현해보겠다.
function naturals(end: number = Infinity): Iterator<number> {
let n = 1;
return {
next(): IteratorResult<number> {
return n < end
? { value: n++, done: false }
: { value: undefined, done: true };
},
};
}
const it = naturals(3);
console.log(it.next()); // { value: 1, done: false }
console.log(it.next()); // { value: 2, done: false }
console.log(it.next()); // { value: undefined, done: true }it.next() 로 지연순회가 지연되지만 아래와 같이 for...of를 통한 순회는 불가능하다.
iteration protocol 에서 규정한 메서드가 존재하지 않기 때문이다.
/**
* Type 'Iterator<number, any, any>' must have a
* '[Symbol.iterator]()' method that returns an iterator.ts(2488)
*/
const it = naturals(3);
for (const num of it) console.log(num);naturals 함수의 iterator가 iteration protocol 규약을 충족시키기 위해선, 아래와같이 코드를 수정한다.
function naturals(end: number = Infinity): IterableIterator<number> {
let n = 1;
return {
next(): IteratorResult<number> {
return n <= end
? { value: n++, done: false }
: { value: undefined, done: true };
},
[Symbol.iterator]() {
return this;
},
};
}
const it = naturals(3);
for (const num of it) console.log(num);
// 1
// 2
// 3naturals() 함수의 반환값에 [Symbol.iterator]() { return {next() {...}}; } 메서드가 추가되었고, 반환값 타입이 IterableIterator<number> 가 되었다.
IterableIterator<T> 는 Iterator 이면서 동시에 Iterable인 값이다.
3. 인터페이스 정의
Iterator<T>, Iterable<T>, IterableIterator<T> 인터페이스의 핵심 기능들만 직접 정의해보겠다.
interface IteratorYieldResult<T> {
done: false;
value: T;
}
interface IteratorReturnResult {
done: true;
value: undefined;
}
interface Iterator<T> {
next(): IteratorYieldResult<T> | IteratorReturnResult;
}
interface Iterable<T> {
[Symbol.iterator](): Iterator<T>;
}
interface IterableIterator<T> extends Iterator<T> {
[Symbol.iterator](): IterableIterator<T>;
}
export {
IteratorYieldResult,
IteratorReturnResult,
Iterator,
Iterable,
IterableIterator,
};4. 내장 Iterable
Iterable이 되기 위해선 iteration protocol 규약을 따라야 한다.
이 사실을 숙지하고, JavaScript의 내장 Iterable 과 Iteration Protocol에 대해 자세히 알아보겠다.
4-1. Array
배열 array는 iterable이다. 그러므로 Symbol.iterator 메서드로 iterator를 생성하고 next() 메서드를 순회할 수 있다. 또한 for...of 문을 사용한 순회 또한 가능하다.
const array = [1, 2, 3];
const arrayIterator = array[Symbol.iterator]();
// iterator 를 통한 지연 순회
console.log(arrayIterator.next()); // { value: 1, done: false }
console.log(arrayIterator.next()); // { value: 2, done: false }
console.log(arrayIterator.next()); // { value: 3, done: false }
console.log(arrayIterator.next()); // { value: undefined, done: true }
// for...of 를 통한 순회
for (const value of array) console.log(value); // 1 2 34-2. Set
const set = new Set([1, 2, 3]);
const setIterator = set[Symbol.iterator]();
console.log(setIterator.next()); // { value: 1, done: false }
console.log(setIterator.next()); // { value: 2, done: false }
console.log(setIterator.next()); // { value: 3, done: false }
console.log(setIterator.next()); // { value: undefined, done: true }
for (const value of set) console.log(value); // 1 2 34-2. Map
const map = new Map<string, number>([
["a", 1],
["b", 2],
["c", 3],
]);
const mapIterator = map[Symbol.iterator]();
console.log(mapIterator.next()); // { value: [ 'a', 1 ], done: false }
console.log(mapIterator.next()); // { value: [ 'b', 2 ], done: false }
console.log(mapIterator.next()); // { value: [ 'c', 3 ], done: false }
console.log(mapIterator.next()); // { value: undefined, done: true }
for (const [key, value] of map) console.log(`${key}: ${value}`);
// a: 1
// b: 2
// c: 3entries(), keys(), values()
entries() 메서드를 통해 Map 객체의 엔트리를 IterableIterator로 반환한다.keys()와 values() 메서드 또한 마찬가지다.
const mapEntries = map.entries();
for (const entry of mapEntries) console.log(entry);
// [ 'a', 1 ]
// [ 'b', 2 ]
// [ 'c', 3 ]
const mapKeys = map.keys();
for (const key of mapKeys) console.log(key);
// a
// b
// c
const mapValues = map.values();
for (const value of mapValues) console.log(value);
// 1
// 2
// 35. 활용
JavaScript에서 Iterable은 언어의 다양한 기능과 상호작용하며 다양한 기능들을 제공한다
(전개 연산자, 구조분해 등)
5-1. 전개연산자
전개연산자(...)는 Iterable 객체의 모든 요소를 개별요소로 확장 하는데 사용된다.
전개연산자를 사용하면 배열이나 객체를 쉽게 복사하거나 병합할 수 있다.
array 병합
const array = [1, 2, 3];
const array2 = [...array, 4, 5, 6];
console.log(array2); // [1, 2, 3, 4, 5, 6]Set → Array
const set = new Set([1, 2, 3]);
const array = [...set];
console.log(array); // [1, 2, 3]가변인자 배열
const numbers = [1, 2, 3];
function sum(...nums: number[]): number {
return nums.reduce((a, b) => a + b, 0);
}
console.log(sum(...numbers)); // 6
console.log(sum(1, 10, 100, 1000, 10000)); // 111115-2. 구조분해 할당
구조 분해 할당은 Iterable 객체의 요소들을 개별 변수에 할당하는 방법이다.
이를 활용하면, 원하는 특정 요소를 쉽게 추출할 수 있다.
원소 추출
const array = [1, 2, 3];
const [first, second] = array;
console.log(first); // 1
console.log(second); // 2head와 tail 추출
const array = [1, 2, 3, 4];
const [head, ...tail] = array;
console.log(head); // 1
console.log(tail); // [2, 3, 4]Map과 for...of
앞서 살펴본, entries() 메서드를 이용하여 키-쌍 값으로 구조분해 하는 코드 또한 이에 속한다.
const map = new Map<string, number>([
["a", 1],
["b", 2],
["c", 3],
]);
const entries = map.entries();
for(const [key, value] of entries) console.log(`${key}: ${value}`)6. 사용자 정의 Iterable과 전개 연산자
앞서 구현한 naturals 함수는 IterableIterator 이다. 그러므로 전개연산자(...)를 사용할 수 있으며, 아래와 같은 활용도 가능하다,
const arr : number[] = [0, ...naturals(3)];
console.log(arr); // [0, 1, 2, 3, 3]마찬가지로 이전에 구현한 map 함수도 generator로 구현하며 IterableIterator를 반환하도록 수정해보자.
import { reverse } from "./ch1-reverse";
function* map<A, B>(
fn: (value: A) => B,
iterable: Iterable<A>
): IterableIterator<B> {
for (const value of iterable) {
yield fn(value);
}
}
const array = [1, 2, 3, 4];
const mapped: IterableIterator<number> = map((x) => x * 2, array);
const iterator = mapped[Symbol.iterator]();
console.log(mapped.next().value); // 2
console.log(iterator.next().value); // 4
console.log([...iterator]); // [6, 8]
for...of 사용이 가능한 iterable 을 두번째 매개변수로 전달 받는다. 그리고 첫번째 매개변수인 fn에 value를 반영한 값을 반환한다.
generator map 또한 iterable 이므로, for...of 순회가 가능하다.
for (const value of mapped) console.log(value); // 2 4 6 8
7. 정리
JavaScript에는 Array, Map, Set 등 다양한 객체가 존재하며,
각 객체는 내부 구조를 공개하지 않으면서도 데이터를 순회하기 위한 수단으로 iterator를 제공한다.
iterator를 통해 전개연산자(...), for...of 등을 통한 순회 등이 가능하며
사용자는 내부구조를 몰라도 iterator.next()를 통해 원소에 대한 정보를 가진다.
이때 순회 자체에 대한 순번 정보는 iterator가 직접 소유한다.
ArrayLike 처럼 iterator를 제공하지 않는 객체에 대해서도,
이를 순회가능한 iterator를 생성하는 함수를 만드는것도 가능하다.
iterator는 순회를 위해 현재 값을 나타내는 value와 다음 원소가 존재하는지 판별하기 위한 done :boolean 속성을 지닌다.
즉, iterator를 반환할 수 있는 [Symbol.iterator]() { return {next() {...}}; } 메서드를 지닌 객체만이 iterable이며,
iterable만이전개연산자(...)나 for...of 등 언어 자체 기능과의 연동이 가능하다.
이러한 iterator를 제공해 줄 수 있는 객체를 Iterable라고 칭하며
Iteration Protocol에 따라 아래 메서드를 제공하는 객체를 Iterable로 규정한다.
[Symbol.iterator]() { return {next() {...}}; }스스로 순회하는 능력을 지녀 iterator로서의 역할을 수행하면서, iterator를 반환할 수 있는 객체를 IterableIterator 로 칭한다.
이경우 대부분의 Symbol.iterator 메서드는 자기자신인 this를 반환한다.
Iterator의 생성함수는 generator(function*)로 간편하게 구현할 수 있으며, 대부분의 상황에서 해당 함수의 반환형은 IterableIterator가 된다.
map 예시처럼 Iterable을 입력으로 받아 새로운 Iterable을 생성할 수 있으며, 이는 Iteration Protocol을 공통 인터페이스로 삼아 컬렉션의 내부 구조와 무관하게 지연 평가 기반의 변환/조합 파이프라인을 구성하게 해준다. 그 결과 다양한 프로그래밍 패러다임(함수형, 스트리밍 등)으로의 전환이 유연해진다