Slide 1

Slide 1 text

イテレータとイテラブルの概要と課題、未 来 JSConf 2024

Slide 2

Slide 2 text

#jsconfjp_b

Slide 3

Slide 3 text

自己紹介 SUZUKI Sosuke ( @__sosukesuzuki ) ● ユビー株式会社 プロダクト開発エンジニア ● 筑波大学 情報学群 情報科学類 B4 ● WebKit コミッター ● Prettier メンテナー

Slide 4

Slide 4 text

この話のモチベーション 日常的にJavaScriptやTypeScriptを書いている人であっても、 イテレータ・イテラブルについてちゃんと理解して、活用して いる人は少ない。 イテレータ・イテラブルは強力な言語機能だから、使うべきと きに使えないのはもったいない。 だから、知ってほしい。

Slide 5

Slide 5 text

この話のモチベーション この発表の目的は、聞いた皆さんが、 1. 日常のプログラミングの中でイテレータやイテラブルを使う ことを考えられるようになること 2. ライブラリ・フレームワークがイテレータ・ジェネレータを 前提にしたインタフェースを持っていても臆することなく使 いこなせるようになること

Slide 6

Slide 6 text

話すこと 1. イテレータ・イテラブルの基本 2. イテレータ・イテラブルのユースケース 3. イテレータ・イテラブルはなぜあまり使われないのか 4. 最新、そしてちょっと未来のイテレータ・イテラブル 5. まとめ

Slide 7

Slide 7 text

1. イテレータとイテラブルの基本

Slide 8

Slide 8 text

イテレータ・イテラブルの基本 イテレータ・イテラブルを一言でいうと 「連続して生成される値を一貫したインタフェースで取り扱 うための仕組み」

Slide 9

Slide 9 text

イテレータ・イテラブルの基本 連続した値を反復して処理したくなることがある。JavaScript でよく使われる反復の対象はArray。

Slide 10

Slide 10 text

イテレータ・イテラブルの基本 for (const foo of bar) {} の of の右側にArrayを書くことが多 い。しかし、実際には イテラブル であればどんなオブジェク トでも書くことができる。 Array、Set、Mapなどの組み込みコレクションはイテラブルで あるため、for of 構文で反復して処理できる。

Slide 11

Slide 11 text

イテレータ・イテラブルの基本 イテラブルとはIterable Interface を満たすオブジェクト のこ と。 イテラブルは、Symbol.iteratorという名前のプロパティを 持っていて、そのプロパティはイテレータを返す関数。

Slide 12

Slide 12 text

イテレータ・イテラブルの基本 余談 ● JavaScriptでは { [symbol]: value } という記法でシンボ ルの値をキーに持つオブジェクトを作れる ● Symbol.iteratorのようにSymbol.hogeという形をしている のは Well-known Symbolsと呼ばれている。JavaScriptの 様々な隠れ挙動を制御するために使われる特別なシンボル

Slide 13

Slide 13 text

イテレータ・イテラブルの基本 ArrayやSetはイテラブル、つまりSymbol.iterator関数があ る。この関数を直接呼び出すこともできる。 ArrayのSymbol.iterator関数はArray Iteratorというイテレー タを返す。

Slide 14

Slide 14 text

イテレータ・イテラブルの基本 イテラブルでなければ for…of の of の右側に置くことはでき ない。意図的にIterableInterfaceを満たさなくなるように Arrayオブジェクトを破壊してみると、エラーになる。

Slide 15

Slide 15 text

イテレータ・イテラブルの基本 説明したこと ● イテラブルであれば for…of で反復処理できること ○ 組み込みコレクションのArrayやSetもイテラブルの一種 であること ● イテラブルは、Symbol.iterator プロパティがイテレータを 返す関数であるようなオブジェクトであること

Slide 16

Slide 16 text

イテレータ・イテラブルの基本 イテレータは、Iterator Interfaceを満たすオブジェクトのこ と。Iterator Interface では、一つの必須プロパティ(next関 数)と二つのオプショナルなプロパティ(return と throw)が決 められている。

Slide 17

Slide 17 text

イテレータ・イテラブルの基本 ※ この発表では return と throw については触れません。すで に色々な人が日本語で記事を書いてくれているので、そちらを 参照してください: ● Masaki Hara. 2021. JavaScriptのIterator / Generatorの整理. https://zenn.dev/qnighy/articles/112af47edfda96 ● uhyo. 2024. イテレータを分岐させるとどうなる? Iterator Helpersに見 るJavaScriptのイテレータの挙動. https://zenn.dev/uhyo/articles/iterator-helpers-iterator-close

Slide 18

Slide 18 text

イテレータ・イテラブルの基本 next、return、throwはIteratorResult Interfaceを返す関 数。IteratorResult Interface は boolean の done プロパティ と、何かしらの値を持つ value プロパティを持っている。

Slide 19

Slide 19 text

イテレータ・イテラブルの基本 シンプルなオリジナルのイテレータ・イテラブル。

Slide 20

Slide 20 text

イテレータ・イテラブルの基本 変数iterableはイテラブルなので、for…ofで反復できる。やっ てみると... 1が無限に出力されている。変数iteratorのnext関数が返してい る value: 1 が element に入ってそうな、感じがする。 イテレータのnextメソッドをちょっといじってみる。

Slide 21

Slide 21 text

イテレータ・イテラブルの基本 iterator は counter というプロパティを持つ。next 関数で counter をインクリメントし、5 以上であれば done が true の IteratorResult を返す。

Slide 22

Slide 22 text

イテレータ・イテラブルの基本 iteratorだけ更新して、もう一度同じコードを実行してみると 書いたとおりに4で止まっている!

Slide 23

Slide 23 text

イテレータ・イテラブルの基本 この動作から、 1. 反復のたびにiteratorのnextメソッドが呼び出されているこ と 2. doneがtrueのIteratorResultを返すと反復が止まること がわかる。 これがイテレータ・イテラブルの正体。ArrayやSetなどの標準 コレクションもこうなっている。

Slide 24

Slide 24 text

イテレータ・イテラブルの基本 Arrayはイテラブルだがイ テレータではない。Array の裏にはArrayIteratorと いうイテレータが隠れてい る。このイテレータのnext 関数を直接使ってみる。 ArrayIteratorのnextは ベースのArrayの要素を一 つずつ返すことがわかる。

Slide 25

Slide 25 text

イテレータ・イテラブルの基本 ジェネレータのことも思い出してみよう。みなさん覚えてます か。redux-sagaでよく書きましたよね。

Slide 26

Slide 26 text

イテレータ・イテラブルの基本 この function* で始まる関数を ジェネレータ関数 という。 ジェネレータ関数は ジェネレータ を返す。

Slide 27

Slide 27 text

イテレータ・イテラブルの基本 ジェネレータはイテレータか つイテラブル。なので、next 関数を持っている。ジェネ レータのnext関数は、ジェネ レータ関数の中でyieldされた 値を生成する。

Slide 28

Slide 28 text

イテレータ・イテラブルの基本 ジェネレータ関数は、yieldで処理を中断し、nextの呼び出し で処理を再開できる関数。 「Promiseはあるがasync/awaitはない」という時代には、 ジェネレータでasync/await風の書き味を実現するライブラリ もあった( https://github.com/tj/co など)。ジェネレータ関数 内でPromiseがyieldされたらresolveもしくはrejectされるま で待ち、resolveされたらnextで再開する。

Slide 29

Slide 29 text

イテレータ・イテラブルの基本 これまでの話を整理すると ● for…ofのofの右側にはイテラブルを書ける ● ArrayやSetなどはイテラブルである ● イテラブルは、Symbol.iterator関数を持つオブジェクトで ある。その関数はイテレータを返す。 ● イテレータはnext関数を持つオブジェクトである。その関 数は { done: boolean, value: any } の形のオブジェクトを 返す。 ● ジェネレータはイテレータかつイテラブルである

Slide 30

Slide 30 text

イテレータ・イテラブルの基本 ちなみに、非同期イテレータ・非同期イテラブルもある。 イテレータのnext関数がIteratorResultオブジェクトを返すの に対して、非同期イテレータのnext関数は Promise を返す。 イテラブルの Symbol.iterator 関数がイテレータを返すのに対 して、非同期イテラブルの Symbol.asyncIterator 関数は非 同期イテレータを返す。

Slide 31

Slide 31 text

イテレータ・イテラブルの基本

Slide 32

Slide 32 text

イテレータ・イテラブルの基本 ● next関数がPromise を返している ● 非同期イテラブルは Symbol.asyncIterat or関数を持つ ● for await of という 構文で反復している

Slide 33

Slide 33 text

イテレータ・イテラブルの基本 これらを踏まえた上で、冒頭の主張について考えてみる。

Slide 34

Slide 34 text

イテレータ・イテラブルの基本 イテレータ・イテラブルを一言でいうと 「連続して生成される値を一貫したインタフェースで取り扱 うための仕組み」

Slide 35

Slide 35 text

イテレータ・イテラブルの基本 イテレータ・イテラブルを一言でいうと 「連続して生成される値を一貫したインタフェースで取り扱 うための仕組み」 イテレータのnext関数に よって生成される

Slide 36

Slide 36 text

イテレータ・イテラブルの基本 イテレータ・イテラブルを一言でいうと 「連続して生成される値を一貫したインタフェースで取り扱 うための仕組み」 for (const foo of bar) {} など

Slide 37

Slide 37 text

イテレータ・イテラブルの基本 ちなみに、イテラブルは他にも色んなところで使える ● for…of ● Array.from ● スプレッド構文 ● 分割代入 ● MapやSetのコンストラクタ ● Promise.all など ● yield* など

Slide 38

Slide 38 text

2. イテレータとイテラブルのユースケース

Slide 39

Slide 39 text

2. イテレータとイテラブル 組み込みではないカスタムの やジェネレータのユースケース

Slide 40

Slide 40 text

イテレータ・イテラブルのユースケース たとえば、こういうユースケースがある: 1. カスタムのリスト風データ構造に対して反復処理がしたい 2. 無限のリストがほしい 3. メモリに載せたくないくらいデカイデータセットに対して 反復処理がしたい 4. 非同期で生成されるデータストリームに対して反復処理が したい

Slide 41

Slide 41 text

イテレータ・イテラブルのユースケース こういう連結リストがあると する。こいつはこのままだと for…ofでループしたり、分割 代入したりできない。 1. カスタムのリスト風データ構造に対して反復 処理がしたい

Slide 42

Slide 42 text

イテレータ・イテラブルのユースケース こういうSymbol.iteratorがあれば、 for…of でループできて嬉しい。 1. カスタムのリスト風データ構造に対して反復 処理がしたい

Slide 43

Slide 43 text

イテレータ・イテラブルのユースケース 大人気のコレクションであるArrayやSetは、すべての要素がメ モリに乗るため、無限だったりめちゃくちゃデカかったりする リストを表現するのが難しい。 2. 無限のリストがほしい

Slide 44

Slide 44 text

イテレータ・イテラブルのユースケース ジェネレータを使うと簡単 に実現できて嬉しい。 generatorのnext関数を呼 ぶと値が1ずつ増えていき、 何度でも呼び出せる。 2. 無限のリストがほしい

Slide 45

Slide 45 text

イテレータ・イテラブルのユースケース メモリに載せたくないくらいめちゃくちゃデカイデータセット があって、それを反復処理したいような状況。 HDD上にあるめっちゃデカイCSVファイルを処理したい、と か。 3. メモリに載せたくないくらいデカイデータ セットに対して反復処理がしたい

Slide 46

Slide 46 text

イテレータ・イテラブルのユースケース 有名なCSVパーサーライブラリ であるnode-csvでは、パース 結果を非同期イテラブルとして 扱える。 3. メモリに載せたくないくらいデカイデータ セットに対して反復処理がしたい

Slide 47

Slide 47 text

イテレータ・イテラブルのユースケース 非同期で生成されるデータのストリームを非同期イテラブルと して表現できれば、for await of を使って反復処理できる。 たとえば、Bunのconsoleオブジェクトは非同期イテラブルに なっている(びっくり!)。 4. 非同期で生成されるデータストリームに対し て反復処理がしたい

Slide 48

Slide 48 text

イテレータ・イテラブルのユースケース consoleのイテレータのnext関 数は、入力された行を非同期で 取得して返す。 Console Standardsにはない挙 動なので否定的な意見もある が、非同期イテラブルの使い方 としては面白い。 4. 非同期で生成されるデータストリームに対し て反復処理がしたい

Slide 49

Slide 49 text

イテレータ・イテラブルのユースケース こういうユースケースに遭遇したら、イテレータチャンス! 1. カスタムのリスト風データ構造に対して反復処理がしたい 2. 無限のリストがほしい 3. メモリに載せたくないくらいデカイデータセットに対して 反復処理がしたい 4. 非同期で生成されるデータストリームに対して反復処理が したい

Slide 50

Slide 50 text

3. イテレータとイテラブルはなぜあまり使 われないのか

Slide 51

Slide 51 text

イテレータ・イテラブルはなぜあまり使わ れないのか 実は、イテレータ・イテラブルを使うべき状況に遭遇すること はあんまりない、という根本的な理由がある。 ArrayやSetを使うべきときに無理にカスタムイテレータや ジェネレータを使うのは悪手。性能は落ちるし使いにくいは ず。

Slide 52

Slide 52 text

イテレータ・イテラブルはなぜあまり使わ れないのか しかし、とはいえ、我々JavaScriptプログラマーはイテレータ ・イテラブルについて無知すぎる、と思う。 ジェネレータのyield, yield*の意味論を知らない。イテレータ ・イテラブル、非同期イテレータ・非同期イテラブルの構造、 作り方を知らない。 コードを書くときに、それらを使うという発想に至らない。

Slide 53

Slide 53 text

イテレータ・イテラブルはなぜあまり使わ れないのか そして、これまでのイテレータはぶっちゃけあんまり便利では なかった。イテレータが便利な言語には、いろんな便利関数が あったりする(たとえばRust)。 JavaScript(ECMAScript 2024時点)では、ほとんどない。 カスタムイテレータを作ったり、ジェネレータを使ったりして も、それを便利に使うことができない。

Slide 54

Slide 54 text

4. 最新、そしてちょっと未来の イテレータ・イテラブル

Slide 55

Slide 55 text

最新、そしてちょっと未来の イテレータ・イテラブル Iterator HelpersというStage 4のECMAScript プロポーザル がある。 2024年10月のTC39(東京開催)で、Stage 4になり、主要な3 つのJavaScript処理系(V8、SpiderMonkey、JavaScriptCore) ではすでに実装されている。

Slide 56

Slide 56 text

最新、そしてちょっと未来の イテレータ・イテラブル Iterator Helpersの前に、いくつか の前提。 Arrayオブジェクトのイテレータで あるArrayIteratorオブジェクト は、%ArrayIteratorPrototype% を継承する %ArrayIteratorPrototype% は%IteratorPrototype%を継承す %ArrayIteratorPrototype% ArrayIterator オブジェクト (イテレータ) Arrayオブジェクト (イテラブル) %IteratorPrototype% Symbol.iterator 継承 継承

Slide 57

Slide 57 text

最新、そしてちょっと未来の イテレータ・イテラブル %IteratorPrototype%は直接アク セスできない、いわば隠れオブジェ クトだった (Object.getPrototypeOfなどを使 うとアクセスできる) %ArrayIteratorPrototype% ArrayIterator オブジェクト (イテレータ) Arrayオブジェクト (イテラブル) %IteratorPrototype% Symbol.iterator 継承 継承

Slide 58

Slide 58 text

最新、そしてちょっと未来の イテレータ・イテラブル 思い出そう %ArrayIteratorPrototype% ArrayIterator オブジェクト (イテレータ) Arrayオブジェクト (イテラブル) %IteratorPrototype% Symbol.iterator 継承 継承

Slide 59

Slide 59 text

最新、そしてちょっと未来の イテレータ・イテラブル Arrayに限らず、SetやMapなどの組み込みイテラブルのイテ レータは、間接的に %IteratorPrototype% を継承している

Slide 60

Slide 60 text

最新、そしてちょっと未来の イテレータ・イテラブル Iterator Helpersプロポーザルは、以下の3つの重要な変更を 加えた: 1. %IteratorPrototype% とそのコンストラクタをグローバ ルに公開した 2. %IteratorPrototype% に便利なヘルパー関数を追加した 3. 普通のオブジェクトにあとから %IteratorPrototype% を 継承させる方法を提供した

Slide 61

Slide 61 text

最新、そしてちょっと未来の イテレータ・イテラブル Iterator Helpersプロポーザルは ● Iterator コンストラクタ ● Iterator.prototype (= %IteratorPrototype% ) をグローバルに公開した。

Slide 62

Slide 62 text

最新、そしてちょっと未来の イテレータ・イテラブル Iterator Helpersプロポーザルは以下のヘルパー関数を %IteratorPrototype% に追加した ● map ● filter ● take ● drop ● flatMap ● reduce ● toArray ● forEach ● some ● every ● find

Slide 63

Slide 63 text

最新、そしてちょっと未来の イテレータ・イテラブル Iterator Helpersプロポーザルは、普通のオブジェクトにあと から %IteratorPrototype% を継承させる方法を提供した。

Slide 64

Slide 64 text

最新、そしてちょっと未来の イテレータ・イテラブル Iteratorを継承したクラスを作ることもできる。                 直接 new はできない。

Slide 65

Slide 65 text

最新、そしてちょっと未来の イテレータ・イテラブル Iterator Helpersがあると、こういうコードが書ける。

Slide 66

Slide 66 text

最新、そしてちょっと未来の イテレータ・イテラブル Iterator Helpersは素晴らしいが、これだけではちょっと... ● map ● filter ● take ● drop ● flatMap ● reduce ● toArray ● forEach ● some ● every ● find

Slide 67

Slide 67 text

最新、そしてちょっと未来の イテレータ・イテラブル ということで、いくつかのプロポーザルがある ● Iterator.zip (Stage 2.7) https://github.com/tc39/proposal-joint-iteration ● Iterator.concat (Stage 2.7) https://github.com/tc39/proposal-iterator-sequencing ● Async Iterator Helpers (Stage 2) https://github.com/tc39/proposal-async-iterator-helpers ● Iterator.range (Stage 2) https://github.com/tc39/proposal-iterator.range ● Iterator chunking (Stage 2) https://github.com/tc39/proposal-iterator.range

Slide 68

Slide 68 text

5. まとめ

Slide 69

Slide 69 text

まとめ イテラブルは イテレータは

Slide 70

Slide 70 text

まとめ ● for…ofをはじめとしたいろんな構文、ビルトイン関数はイ テラブルを受け取る。 ● 以下の場合に、イテレータ・ジェネレータとして表現する ことを検討しよう ○ カスタムのリスト構造を扱うとき ○ 無限のリストを扱うとき ○ メモリに載せたくないくらいデカイデータを扱うとき ○ 非同期に生成されるデータストリームを扱うとき

Slide 71

Slide 71 text

まとめ ● 最近のJavaScriptには、Iterator.prototype にいろんなヘ ルパーが追加されている。 ○ 自分のオリジナルイテレータに Iterator.prototype を 継承させれば(Iterator.from や Iterator コンストラク タによって)ヘルパーを使える。 ● 他にも新たなヘルパーを追加するプロポーザルがアクティ ブに動いている。