RxJSをもくもくしてReactivePropertyの価値らしきものを気づかされた話(仮)
Reactive Extensions JS port(RxJS)をもくもくしていた結果、C#界隈がReactive Propertyを作り出した理由が(なんとなく)わかってきた。
Rxの流儀にのっとれば、ある値を使おうとする場合、その値を生成するObservableの結果を受けて、そのObservableも同様に……とObservableの依存関係を作っていく必要がある。
Rx.Observable.do()
メソッド中に、関数外のグローバル変数などを副作用なmutateをさせてもいいが、これは基本的によろしくない。Rxで実行される各Observerが、一体どのタイミングが動作するかの順序保証が本来的には存在しないためだ。イベントループがひとつしかない & Workerは完全にメモリ空間が分割されたアクターライク & Rx.Scheduler
もDOMのイベントループ原則に則るJavaScript portではそこまで極端な問題にはならないかもしれないが、その他多くのメモリ空間共有型のスレッドを用いる種類の言語では容易にdata raceなどの問題を抱えることとなるように感ずる。
この問題を避けるには依存関係にある値のObservableを愚直に紡いでいけば良いのだが、従来の手続き型プログラミングに慣れた我々には慣れない面があるし、明らかに冗長とも思えるケースがある。突如として飛び道具的に副作用を起こして問題を解決したい場合もある。行儀がいいとは言えないが、GUIプログラミングではこういう需要はままある気がするし、そもそも言語が副作用の存在を許容している以上、その帳尻を合わせる為にどこかで泥を被る必要がある。
こうした問題に対してC#の人々が考えついたアプローチがReactivePropertyであるということに気づいた(名前だけは知っていた)。値そのものをRx.Observable
に変換できるコンテナを用意し、可逆的な変換も可能なコンテナ。値の変更が起これば、そのイベントをObserverへと伝播させる。
値のmutateは副作用であるが、Observableのチェーンをつなぐことで依存関係を表明することができる。サンプルコードを見るだけでは結構危なっかしいし、GCあり言語だなと感じる面もある。が、値のmutationに伴って、イベントが常に流れ続け、それが最終的にatomicに値を上書きするのだと気付くと、妥当感にあふれる。いい意味で言語的クリーンさを無視している。良い。
さっそくRxJSでも使ってみようと思ったが、同名かつNode.jsのEventEmmitterを使ったライブラリは何点か見つかるが、Rxベースのものはない。これはライブラリ作成のチャンスかもね(本業でRxを実戦投入するには必要と思われるので、その時は自分でなんとかするかもしれない)。
しかしこうして見ると、言語に後付けて導入したモデルとしては、Reactive Extensionsは本当に良い塩梅で出来ている。もちろん私が地雷を踏んでいない可能性もある。API designがVisual StudioのIntellisenseによる補完が明らかに前提となっていて、IDEの支援無しに書くには二の足を踏むのは否定できない(Visual Studio + TypeScriptで解決するとは思うけど)。ただ、これを見た後にES7 Object.observeをみると、Promiseなど以前のパラダイムに生きているなんとも中途半端感を感じずにはいられない。
「オメー何言ってるんだ、それは違う」というC#系の方がいらっしゃいましたらツッコミをお願いします
makeのくびき
「gulpって何だよ、makeでいいじゃん(要約」論争について、私もちょっと一本講釈をぶってみることにする。あれやこれやといった実利的な話をするつもりはない。そういうものは既に書いた人がいるのでそちらを参照のこと。
Gruntの思い出
Gruntは、私の印象で言えば車輪の再発明の失敗作のようなもので、タスク間の依存関係が破滅への一途をたどり管理不能に至るなど、宣言型の負の側面が強く出てしまった。しかし、設定は本当にサンプルコードのコピペだけで組み立てられるので、JSが不得手なデザイナーなどには非常に受けが良かったという点は忘れてはならない。ちょうど、html5ブームが本格化して, Apache Antとかに慣れ親しんだJava(主にSIer)系の人が入ってきたタイミングにあった道具かつ、Yeomanファミリーにも組み込まれており、それでいて簡単な事をやらせるには悪くはない具合のシンプルさ、そして設定ファイルがホスト言語で書けるという、「悪くない」塩梅だったために流行ったというのが私の感想。あ、ファイル監視によるタスクの自動実行機能いわゆるwatchも流行った一因だろう。ただ、まあ、GruntであればMakefileを書くのと大差ないと言われても仕方が無い、むしろmakeの方がまだマシだと言われても仕方が無いようには思う。私もそこについては割と同意(というか、私はGruntfile書くなら俺はMakefile書くぜ!な人間だったので、まんまそういう意見だった)。
gulpたん
Gruntがそこそこ使われるようになってきて、「これって色々やり始めるとイマイチじゃね?」という認識がアーリーアダプターの間で醸成され始めた矢先に出たのがgulpだったように思う。これは中々にmodernな着想に立っており「ビルドプロセスとは、ファイルストリームに対してtransform関数を適用してmapを行うものだ」という前提に立っている(意訳)。パイプラインの各タスクの単位を明確にmap関数に切り分けたので、処理パイプラインがJavaScriptのfunctionとして明瞭になった上、streamとしてオンメモリでの処理が行われるようになったため、tempファイルを作るなどのアプローチに頼っていたGruntに対する速度的な優位も生まれた(そして明らかに初期は「Gr**tに比べて速い」を売り物にしていた記憶がある)。gulp流のStream哲学に合わないサードパーティのtask pluginをブラックリスト認定するなどの行動にも出るなど開発陣は極めて急進的な側面もあるが、思想という点では中々にmodernな路線に進化する事になった。
けれども、gulpには問題点があった(と認識している)。せっかくプログラマブルかつ関数をタスクの単位としたのに、肝心のタスクとなるmap関数を作ろうとすると、gulpのstreamに併せて変換メソッドを隠す為にdirtyな関数を作り、クロージャとしてタスクを返すスタイルが、処理モデル上、強制されるので、その局所的に発生した汚物を隔離する為にtask pluginの形式でパッケージ化する必要が出てしまった。これはGrunt文化を引きずっている面もあるだろう。結果、「関数を組み合わせる事が出来る = 本来的には各種ツールチェインがビルドシステム構築用に提供しているAPIをそのまま使う」という事ができずに、隠蔽の向こう側に一段置いてから使う羽目になってしまった。本家のトランスパイラではなく、task pluginのアップデートに私のビルドチェーンが左右される日々の始まりである。peer dependency指定なら良かったね? いやいや、peer dependencyだってsemver的にmajor upがかかったら対応待ちだぜ?
理想的なgulpに必要だったのは、そこらへんの普通のトランスパイラのAPIをgulp streamに対応したtransform関数に変えるビルトインファクトリだったのかもしれない。これがあったならgulpはhigh modernなビルドシステムなり得たに違いない!(と私は勝手に思っている)。もしくは、Rxのような、もっと別物の抽象機構に乗っかっておくべきだったか。いずれにせよ、これらは夢物語である。
もうすこし汎用ビルドシステムの四方山話は続く。JavaScriptの変換に限ると、asterと呼ばれるツールが登場した。「お前らfile streamとか言うけど、jsファイルをやり取りしてるじゃねえか。毎回パースするの無駄だからASTを直接流そうぜ」というアプローチ。コンパイラとしては非常に正しいツッコミ。だが、これはイマイチ市民権を得られていない。考えられる理由は、シンプルに言ってgulpを上回る魅力が無いからであろう。コンパイラ的に正しいと言っても、それはアーキテクチャが綺麗というだけで、これで変換したコードからバグが消える訳でもない。毎回パースを行い抽象構文木に変換する処理は確かに計算機資源の無駄だが、そんなものはハードウェアの進歩で解決している。GruntではディスクI/Oがボトルネックだったが、gulpではstreamスタイルを強制した事で解決「させた」。というかそこが重要ではないのは, Grunt、gulpともにデフォルトでインクリメンタルビルドのためのツールチェインを備えていないことから明らかであろう。変更監視と都度フルビルドの体力勝負で賢いビルドが悩みがちな問題を解決してしまったのだ。それと、エコシステムの問題。いくら「フロントエンドWeb」が浮気性だからと言っても、ビルドツールという彼女を何回も乗り換えるほどの好色はそこまでいない。おまけに各種変換ツールチェインはES3~5のコードこそを中間表現として、そこに対して独自のASTを生成し独自の変換技法を尽くしているのが当たり前になった今更になって、各自の手の内を標準化しようなどと言うのはいささか困難な話であろう。
と、いうのがgulp一派に対する弁護と批判と多くの私情を交えた説明。
コマンドラインが一級市民の国
で、TypeScriptとかbrowserifyとかbabelとかfacebookのreact-toolsの話に戻ると、彼らはあくまでもコマンドラインからの使用を一般的な利用におけるfirst class citizenに取り扱う。一応、謎ビルドシステム・謎IDEからも遊べるようにnpm packageとしてのプログラマブルな利用も可能だぜ、というスタンスに近い。gulp pluginを公式にメンテしたりもするが、あくまでもtier 2であり、公式でなく野良協賛者が出してるgulp pluginともなると、公式も薦めこそするが「こんなのもあるから使ってね!」みたいな扱いに近いように思う。
なので、件の記事で話題になっているTypeScriptやbrowserifyについては、そもそもgulpとの相性が悪い道具なので、「makeの方が良いじゃん!」と言われたら、「そうですね」と頷くより他は無かったりする。
makeじゃダメなの
結局、makeじゃダメなん?という疑問については、私はダメじゃないと思うけど、なんだかんだで.PHONY
とか書かなきゃいけないのだし、謎オプションを調べるのとgulpのタスクをコピペするのは特に大差がないし、むしろプラガブルな設計してるものをコピペする方が楽だし、だいたいプロジェクト始めるときに1回調べるだけなのは一緒じゃね?(後は使い回しになるよね?)と思う。
gulpの利点としてプログラマブルであることが挙げられる点についても、「そもそもビルドツールに条件分岐などのプログラマブル性を求めるべきではない」という声はあるものの、そうは言っても人類は、今までAutotoolsとか、その他メタビルドシステムを(makeをバイトコードと看做す形式で)何度か発明してきてしまったわけで、いずれはどこかでプログラマブルな需要を吸収しなければならないので今更な問題のように思う。まあ、程度問題として全部meta-metaにやられても困るので、どの程度までプログラマブルを許容するdesignにするかという問題はあるのは同意だけど、それはもっとミクロな個別の問題だと思いたい。
そして、プログラマブル性を求め始めると、なんだかんだで完全ないし高水準なスクリプティング機構が欲しくなるので、ホスト言語で書けるように作りたくなるのは仕方ないんじゃないかなとは思う。特にコンパイラのオプションとかは、3つで済む筈が無くて、それをコマンドライン1行で書くのは地獄だし、幾つもオプションを作るのも冗長なので、スクリプタブルなフラグで管理したいという気持ちは尤もであろう。
それにJavaScriptの場合、Cとかのような原始的なincludeベースのビルドシステムじゃなくて、コンパイラがエントリーポイントさえ分かれば後は依存関係を全部掌握してしまうモデル(というかコンパイラが明示的に情報を出さないと取れない)ので、GNU Makeの機能をフルに使ったインクリメンタルコンパイルなんてものは期待できない。結局、.PHONY build
とか書かないと、エントリーポイント以外のファイルを弄った場合にビルドできなかったりしたり(もちろん私のmake力が足りない可能性は十分にありますよ、ええ)。
ともかく、なので「Makefileで完結するじゃん!」なんてことはそんなに無くて、共通インターフェースのコマンドディスパッチャとしてMakefileを書きつつ、実態はgulpなり好きな物に投げろと言う感じになる。ここらへんはMozillaのmachとかを連想してもらえればわかりやすいと思います。
この手の議論をするときに捨て置かれがちな話に、クロスプラットフォーム対応の話がある。ついつい世の中のソフトウェアエンジニアは * nixでしか仕事しない前提で話を進めてしまったりするのだけど、特にWebエンジニアはOSXかLinuxしか使ってない前提で話してしまうのだけど、世の中そんなことはなくて(当たり前だよね)、Webフロントエンド(Web制作)の場合、エクセル入稿やワード入稿とかがあったりするので、Winで開発してる人がそれなりに居たりする。そういうのが無くても、Windowsで開発していたりする。別にIE対応がどうとかじゃなくて。そういう人にとっては、cmd.exe叩いても動かない可能性が非常に高いmakeよりも、node.jsさえ動けばとりあえず動くGruntとかgulpの方が遥かに簡単だったりする。黒い画面は恐ろしいのだ。少なくともホスト言語の環境が動けば、とりあえず動くというのは非常に強みで、Gruntがなぜあそこまで流行ったのかは、クロスプラットフォーム耐性の高さとコピペビリティの高さだと言っていいと思う。もし、あれが*nixでしか動かなかったら、ああは流行らなかっただろう。みんなbuild.shかMakefileかbuild.batを書いている。おそらく今でも。
そういう諸々の状況を考えると、「makeでいいじゃん」というのは頭では納得がいくし、確かに壮絶に車輪の再発明を回させられている趣はあるのだが、なんだかんだで再発明された車輪を回した方が便利なケースも結構あるよねと感じるわけです。思想的には最も急進派であるgulpも、gulp pluginを使わずに自分でモジュール呼んだりchild_process.spawn
したものを呼び出すだけみたいなハイブリッド運用で回すといい具合に適材適所になったりする。
コピペでビルドツールを作るのか?というのは、確かに知力を武器として飯を食うエンジニア的には敗北感はある。が、落とし穴を掘りにくいdesignであるならばコピペビリティは正義である。我々は最強のビルドツール設定を書きたい訳ではなく、とにかくビルドチェーンを作りたいだけなので、コピペビリティ上等みたいなpragmaticぶりで良いのではないかと思う。いやあまあ、同僚がクソみたいなビルド設定作ってきたら、悪態をつきながらコードレビューで闘えば良いのではないかな。闘うのが面倒くさくなったら、コッソリ好きなスタイルに上手い事リファクタリングだ。
というわけで、なぜ人は再発明を繰り返すのか?という問いについては、一種の諦めと、ツールチェインの特性の問題と、「何十年も前に作られたCとUNIXの降霊方法を今更覚えたい若者はそんなに居なかった」という結論で良いんじゃないかと思います。C/C++、Javaに次いで10年ぶりにビルドツールチェインを作る機会に恵まれたのだから、知恵は活かしつつ新しいものを使いたいのは人間の好奇心。コンパイルと言っても、C/C++やJavaに比べればさしたる作業をしているわけではないのだし、コンパクトで調教しやすい物で良い。これで解決できないものが出来たら?今のJavaScriptの領分ではないので諦めてC++を書こう。GYPが君を待っている。個人的にはRust(のcargo)にも頑張ってほしい。
あ、余談だけど、この分野、たぶんgolangで依存関係を書けるコマンドディスパッチャを上手い事作れると、一発当てられそうな機運はあるなーと思った。既にあったかもしれない。ハードボイルドな燻し銀のgolangを使えば、おじさんも若者も大満足でwin-winなんじゃないかと。golangの特性的に妥当な感じもあるし。それがJavaScriptの文化に受け入れられるかは知らないけれど。
リュックサックを新調した話
リュックサックが欲しくなった
大学生3年の頃に、BERMASの42cmのブリーフケースを買って以来重宝している。単体で1.5kg弱あるんだけど、その変わり生地が非常に硬く、型もしっかりしている上、おまけにショルダーベルトまであるので非常に使い勝手がいい。ポケットなどについてはリンク先を見てください。
大きな収納スペースが2つあるので、コミケのときは買った同人誌を一方のスペースに片っぱしから放り込むのに使い、もう一方に500mlペットボトルを2〜3本格納したりできるし、旅行のときは1泊程度であれば着替えやPCを入れたまま行動しつつサイズによってはお土産を格納できるくらいの容量があり、それでいて型崩れなども特にないので非常に便利に使っている(正確にはマチを広げるためのファスナーを一度修理したことがあるんだけど、実用上は必ずしも必要ない場所のファスナーなので、かなり頑丈です)
[バーマス ファンクション] BERMAS FUNCTION ブリーフ42 2層EX 60135-10 ブラック
- 出版社/メーカー: BERMAS(バーマス)
- メディア: ウェア&シューズ
- クリック: 2回
- この商品を含むブログを見る
が、いかんせん日常の通勤に使うには単体1.5kgは重い上、ポケットの数や頑丈さもオーバースペック気味。なので、軽く散歩するときや通勤時は、高校生の頃に買った小さなショルダーバックを使うようにしていた。とはいえ、このショルダーバックは、当時地元のイトーヨーカドーで4千円くらいで買ったもの + 結構酷使してきているので、いい加減に生地の痛みが激しくなってきてしまった。PCなどを入れると形が崩れる上、内容量もMacBook Air 13などを入れるとそれだけでいっぱいになってしまう程度のサイズ感。おまけにショルダーバックということで歩いたときの斜め掛けが微妙な感じがあったので、新たな鞄をさがすことにした。
紆余曲折
探すに当たって設定した条件は以下の通り。
- リュックサック型であること
- 容量は大きくなくていい(大容量の用途には最初に述べた42cmのブリーフケースで十分だし、それで入らない荷物は私の日々の生活では扱わないし、必要になったら巨大なリュックサックを買えば良い)
- それなりに頑丈であること
リュックサックを選んだのは、運用上、両手を開けたいため、かつ片方の肩にばかり負担をかけたくなかったから。 容量は、日常使いのものなのでそんなに大きくなくていい。 頑丈さは、まあ、そりゃ求める。まして今使っているものが型崩れを起こした果てのものだから。
秋葉原のヨドバシカメラでのんびり探していたのだが、結構難航した。頑丈なリュックサックを求めると概して大きなものになってしまうか、見た目がビジネス向けすぎてダサいもののどちらかになってしまう。かといって小さいものを求めると、作りが雑だったり、見た目がいいものは本当に携帯と充電器とハンカチと財布とかしか入らないようなサイズになる。もしくはビジネスマン向けのダサい感じ。悩ましい。
邂逅
結局、今日も収穫は見つからないのか、やはり上野あたりでいろいろ店を回った方が良いのかと失意にくれかけた瞬間に、Timbuk2のQ BACKPACKを発見した。掘り出し物だ。棚の一番下の段の奥の方に眠っていた。容量はそこそこでお大きすぎないが小さすぎでもない、ポケットの数もまあそれなり。それでいて作りがしっかりしている。マチはそこまで太くないので嵩張らないし、背負ってもバックパックが後方に盛大に突き出すような不恰好な感じにはなりにくい。デザインも悪くない。いい。値段も8千円前後なので、失敗してもそれほどダメージは負わない価格。売り場の同価格帯の製品に比べると、1万円を超えていてもいい感じの作りをしているので、お得感はある(実際、メーカー希望小売価格だと1万円をはみ出ている)
というわけで、これのディアボロ(赤系)を買ってきましたとさ。他の色が無かったから詳細な比較検討はできなかったんだけど、割とカジュアル用途の鞄まで黒単色にはしたくなかったし、深いワインレッドでそこまで派手ではなかったこと、他の色がそこまで好みのトーンでもなかったので、結局置いてあった色でいいだろと判断。とはいえ、Martini Olive Surf Stripe(明るい緑)あたりは実は気になっていた。でも結構色が明るいので、そのうち飽きそう。 カーボンフルサイクルツイル(カーキ系だけど灰色に近い?)は、もっと緑・茶系だったらいいと思った(でも違う感じ)。てなわけで消去法でディアボロ(赤系)を選びましたとさ。
[ティンバック2] TIMBUK2 Q BACKPACK 396-3-6061 ディアブロ (ディアブロ)
- 出版社/メーカー: TIMBUK2(ティンバック2)
- メディア: スポーツ用品
- この商品を含むブログを見る
使い勝手どうよ
買ってみてだいたい3週間くらい使ってるんですが、マチが細い割に結構いろんなものを投げ込めるので重宝してます
option-tにPromiseへのキャストメソッドを用意したりなど
自作RustスタイルECMAScript用Option型ライブラリoption-tをいろいろアップデートしたので、リリースノートがてら書く。ご存知ない方はこちらをどうぞ。要はOption<T>
/Maybe
型だ。
Promiseへのキャストメソッドを用意した
ECMA 262の文化には、すでにMaybe
もどきの標準仕様が存在する。ご存知Promise
だ。
だが、Promiseは非同期前提の操作になるので、同期的に実行するAPI群との親和性が良いとは言えず、かといって全てをPromiseにするほどでもない場合のために、わざわざこんなライブラリを作ったというのは前に書いた通り。
でも、今の世の中、どこかしらの関数がPromiseを返すじゃん?そこに上手く混ぜられるととっても楽じゃん? てなわけでcast用メソッドとしてOptionT.asPromise()
を追加した。これにより、スムーズにPromiseと混ぜることができ、非常に最高な気分になれる。
こんな感じ。XHRを投げつつ、別のフラグの有無を確認し、両方が成功した場合に〜みたいなケースに有用になりました。
var option = new OptionT.Some(); var xhr = new Promise(); Promise.all([ option.asPromise(), xhr, ]).then((tuple)=>{ var responce = tuple[1]; ... });
ちなみに、Promise<T>
-> Option<T>
の変換は、非同期から同期への変換なんで無理ですやってない。
いろいろメソッドを追加した
追加しました。前よりも楽に書けるようになったと思います、どんなの使えるかはgithubのissue一覧かCHANGELOG.mdをみてください。
ここで注意点がひとつ. Option<T>.mapOr()
とOption<T>.mapOrElse()
については、unstable APIという区分にしてある。理由は、取りうる仮引数の順序が、「デフォルト値生成」 -> 「map関数」の順序になっているため、直感に反するのではないかというのが理由。元のRustのstd::option
のAPIが微妙っちゃ微妙なので、Rustの動向を無視してもいいのだが、ちょうど今のタイミングで、Rust側でも似たようなissueが立っていて、できることならもう少し様子見したいというところ。なのでunstableという区分にはしている。
まあ、変更するとしても仮引数の順序を入れ替えるだけで、挙動としては変わらないのと、そういう破壊的変更するときはsemver的にメジャーバージョン上げるので、使うのをためらうほどではないかもしれない。ただし、今の所はNODE_ENV = 'production'時以外は``console.warn()
でうるさく警告出してるので気をつけて下さい。
一応フィードバックありましたらお知らせください。
Option/Maybeとかで解決していることを、さながらgolangのようにES6のdestructuring assignmentで解決する
ES6から使えるようになるdestructuring assignmentを使って、タプル的に返して受け取れば、複数の値を楽に受け取れるので、エラーハンドリング的なものが楽にできるようになりますよね!という話です。先日書いたライブラリで解決・緩和しようとしていた問題に対する別のアプローチ。
今更言うまでもない、おそらく常識的なテクニックの話なんですが、みんなES6の話をしているのに、こういう使い方の話をロクにしていないので「こういう話しようぜ!」と喧嘩を売る意味で書きました。
let fn = function () { return [ true, // 関数が正常処理できたか or 値の有無とか 100, // 実際の値 ]; }; let [ok, val] = fn(); if (!ok) { return; // 正常処理できていない, 値が特になかったので帰る }
まあ、まんまgolangのmultiple resultsなんだけど。
golangのmultiple resultは、手続き型の枠内で、Maybe
とか入れずにMaybe
で解決しようとしていた問題に(過程は知らないが結果として)取り組んだアプローチとして、ああなるし、よくできているなあと思う次第です。
ES5の範囲でOption<T>型を表すライブラリ、option-t を作った
動機
初期状態で未選択なラジオボタンがあるようなフォームを作っている場合、ラジオボタンに対応するモデルの値を「この値は未選択である」というのをJSで表現するのは結構面倒くさい。チェックボックスであれば, booleanのどちらかで状態が確定するが、ラジオボタンだと取りうる値は複数になるし、初期状態で選択されているか否かの問題が発生する。選択されていない状態を専用にフラグとして持つのは気持ち悪いが、かといって、未選択の状態を-1
や9999
ないしnull
、undefined
で表現するのは危うい。コードを書いた本人しかわからない。
RustやScalaなどのようにOption<T>
/Maybe
がある言語なら、こんなまどろっこしい思いはせずに、明示的に値の有無を表現できる。
というわけで、ないなら作ってしまえば良いじゃないメソッドで作った。
Option<T>
型について
私が説明するよりもわかりやすいであろう先行の説明があるので、それを紹介する。
- Option type - Wikipedia
- 「値が無い」ということをどう表現するか、あるいはOption型について - Line 1: Error: Invalid Blog('by Esehara' )
作ったもの
元々、仕事で書くコードの為に作ったものなので、もしかしたら会社のgithubアカウントの方に移管するかもしれない。
設計思想
Option<T>
をECMA262 5thの範囲内で実装する- APIモデルについては、Rustの
std::option
をベースとした - 詩的な名前は思い浮かばなかったので質実剛健な名前にした
- あまり文学的な名前にしても後で忘れる。
- 主対象環境は、TypeScriptおよび生のES5
使い方
こんな感じ
var OptionT = require('option-t'); // `Some<T>` var some = new OptionT.Some(1); console.log(some.isSome); // true console.log(some.unwrap()); // 1 // `None` var none = new OptionT.None(); console.log(none.isSome); // false console.log(none.unwrap()); // this will throw `Error`.
おなじみのmap()
, flatMap()
の他に、値を取り出す為のunwrap()
、デストラクタ代わりのdrop()
あたりを実装している。最終的には、Rustのstd::option
のAPIのほとんどを実装したいところ。
Some<T>
とNone
のインスタンスがOption<T>
であることを、どう表現するか?
これが一番悩んだ。最初はOptionType
のような一つのオブジェクトを用意し、コンストラクタに渡す引数がundefined
か否かを確認するようにしていたんだけど、「正常系でundefined
を返すケースでも、暗黙的にNone
に変換されてしまう」ため、断念して、結局別個のオブジェクトを作る事になった( OptionType
については移行期間中のため、deprecated扱い)
ここで、それぞれのオブジェクトがOption<T>
であることを表現する為に、Option<T>
というインターフェースを用意して、TypeScript向けにはそれぞれがOption<T>
を実装していると見せることにした。
一方、生のJS向けにはOptionT
というオブジェクトをプロトタイプチェーンの祖先に突っ込んで(基底クラスに突っ込んで)、option instanceof OptionT
で確認できるようにした。が、これについてはあくまでも型システムにインターフェースを所持しない環境向けであり、TypeScriptのようにインターフェースが普通に使える環境には露出させていない。
Promise<T>
をMaybe
モナドのように使うのではダメだったか
2015年の我々には、モナドと非同期コンテナを混ぜ込んだ、Promise<T>
という共通インターフェースがある. これを使うのも考えたが、少なくともES5にはawaitに相当する仕組みが無く, Promise<T>
では値を同期的に取り扱うことができないので止めた。RxなどのObservable<T>
についても同様。同期的に扱えるコンテナが欲しかった
なぜES5? ES6でもbabelでも良いじゃん?
ES5で十分に書ける範囲の内容なので、transpilerは(要らないと)判断した. そのうち気が向いたらTypeScriptやbabelに移行するかもしれないけどね。
browserifyありきなんですが
browserifyありきですね
先行事例
作った後に気がついたんだけど、極めて類似の先行事例にopty (npm)があった。こちらはRustのAPIモデルをベースにTypeScriptで書いたもの。
先にこっちに気がついていればoptyを使う可能性もあったけれど、テストコードが無かったのと、JSONシリアライズ時の表現が特に記述されていなかったのと、RustのそれよりもAPIが(無駄に)増えていたので、音楽性の問題で当面は別個に開発するつもり。将来的には、あっちに合流して大統合してもいいかなとは感じる
まとめ
感想お待ちしております