Flutter の Stream から見る Reactive Programming の入り口【エンジニアブログ】
はじめに
Flutterを開発していると、Streamというクラスによく出くわします。Firestore や FCM の SDK がインターフェイスとして公開していたり、BLoC パターンでも頻出するアイツです。
Stream の理解で躓く方、多いんじゃないかと思ってます。特徴を掴もうにも、「非同期なデータのシーケンスを扱える」だとか、「まるで川のように上流から下流に向けて値を流す」だとか、いや理解はできるんだけど腹落ちしないとうか。実装例を見たって「それ Stream 使わなくても良くね?」ってなる感じ。
先に言うと、Stream を完全理解するには Reactive Programming という世界を先に理解することが必要です。世界です世界。パラダイムとか新時代でもいいです。ただ、これの完全理解には時間がかかるし、完全理解したとしても Flutter 開発への寄与率は少なく、コスパが良くないです。多分。
本記事では、そんなあなたに腹落ちを与えるべく、Stream から見る Reactive Programming の入り口を解説します。本記事を読み終えても Stream マスターになれるわけではありませんが、ちょっとだけ楽しく Stream を扱えるようになっていて欲しいなと思っています。よろしくお願いします。
1. 背景から Reactive Programming を知る
2011年、マイクロソフトによって ReactiveX というソフトウェアが作成されました。このソフトウェアには大きな思想があり、
同期か非同期かを区別せずにデータを扱いたい
命令的プログラミング言語であっても上記を宣言的に扱いたい
という、「非同期処理、つらいよね」という当時のプログラミング全般が抱えていた問題の解決を目指していました(要出典)[1]。
シーケンスという考え方
ReactiveXは、同期的に用意されるデータと非同期的に用意されるデータを区別なく扱うために、「シーケンス」という概念を用いました。シーケンスとは、順序のあるデータの列です。よく目にするのは配列だったりListクラスでしょう。
仮に次のような突拍子のない提案を想像すると、
ただの値と配列を区別して扱うの大変だよね?全てのデータは配列にしちゃお😉
ReactiveXは、これを同期か非同期か?に関して行ったイメージです。
同期処理と非同期処理を区別して扱うの大変だよね?全てのデータはストリームにしちゃお😉
これがあの Everything is a stream. と言われる所以です。もちろんストリームもシーケンスです。ReactiveXは、このような思想のもと「同期的か非同期か区別せず、シーケンス化されたデータを扱う」ことのできるソフトウェアとなっています。
※ 「ストリームって結局なんやねん!」って突っ込みは次章までお待ち下さい
ざっくり歴史を想像すると
ReactiveXの歴史を想像すると、
マイクロソフトは、プログラミングの新時代を考えた
マイクロソフトは、.NET Framework 向けの ReactiveX ソフトウェア(Rx.NET)を開発した
マイクロソフトは、他のフレームワーク向けの ReactiveX ソフトウェアもどんどん増やしていった
Reactive Programming に共感したデベロッパによるソフトウェアも増えていった
今に至る
という、多くデベロッパを巻き込んだある種の変革のようなものを想像できます(要出典)。エモいですね。
一旦用語を整理すると、
です。
dart Stream の位置づけ
僕の知ってる範囲で Reactive Programming 関連のソフトウェアを紹介します。
こんな関係図になりますね。実際にはまだまだ他にもありますが。
※中の人、実際はうおおおおおとか思ってなかったらごめんなさい
実際のところ、dart:async や ReactiveSwfit は、ReactiveX と比べると設計が結構違ったりします。根本の「同期か非同期かを考慮せずすべてのデータをシーケンスとして扱う」は同じなものの、「良いインターフェイスは何か」に関して製作者の意志が強く反映されている印象です。クラス名ですらこんな違うよ、ってのを簡単に紹介です。
やっと出てきましたね、Stream。2000文字越しに伏線回収した気持ちです。Stream はこういう世界の末端に位置する存在です。
Stream 関連について少し補足すると、上記の通り ReactiveX とは結構インターフェイスが異なっています。また、ReactiveX には 死ぬほどたくさんオペレータがありますが、dart:async がサポートしているのはこれらのうちごく一部です。憶測ですが、ReactiveX の最たる課題感として「学習コストが高いこと」があり、dart:async はこれを回避すべく必要最低限に絞ったんじゃないかなと考えています。慣れている身としては「おぉん?」って感じですぐ RxDart 入れたんですけどね。
Reactive Programming の雰囲気を掴んでもらえたでしょうか。「同期か非同期かを区別せず全てのデータをシーケンスとして扱う」という思想のもと、マイクロソフトを始めとした各種デベロッパーがソフトウェアを開発しています。dart の Stream、もとい dart:async もこのソフトウェアの1つです。
2. データの性質から Stream の立ち位置を知る
第1章では、Reactive Programming を背景から見ていきました。なんか雰囲気は分かったけど雰囲気しか分かってない状態かと思います。第2章では、シーケンス化されたデータと、普段の Flutter 開発で使うデータが、いかに似ていて拡張概念であるかを説明します。
なお、本章の説明は ReactiveX のintroduction と重複しています。このページを読んで「あーなるほどね」を得られる方は飛ばしちゃっても良いと思います。
toridoriiのプロダクト開発部では通年採用を実施しています!
ユーザの幸せについて真剣に議論したり、「やってみたい」で新しい技術に挑戦をしてみたり、気づいたら30分雑談していたり、ゆるく真面目に開発しています。もし興味を持ってもらえたら、こちらを読んでみてください!
脚注
つらさの根源は命令的プログラミングにあるものの、Webアプリケーションのリッチ化に伴って非同期処理を扱う機会が増え、つらさが表出した、みたいな印象を持っています