neverthrowと比較したoption-tについて

たまたまoption-tを紹介してくれるブログ記事があったみたいで、その上でneverthrowの方がおすすめみたいな感じのtweetを見かけて、色々言いたいことがあったので久々に書いてみる。

あ、元々のブログについてはあんまり思うことはないです。「まあ、プロジェクトごとに好きにすればいいんじゃない?」って感じ。try-catchが良いか悪いかは山のように議論があるから、そっちでやってくれ。

自分の好みを問われるならば、それはoption-tのMotivationを読んでくれとなるが、その上で、 プロジェクト固有のconvention(規約)や設計指針・依存するライブラリとの接続によっては、必ずしも Result ( Either )型を使うのがベストな選択であるとは限らない のは勿論だと強調しておきたい。

ECMAScriptと関連するspec群においてtry-catchと例外の機構自体は一級市民であるのは揺るぎもない事実であり、それを無理に自前の規約でひっくり返すのが良いかは場合による….としか言いようがない。素直に例外とnull武装する方がいいことも多い。そのために T | nullとかT | undefinedを対象にしたツールキットをoption-tは提供しているわけだし。

あ、option-t関係ないけど、Error.prototype.causeについては本当に便利で最高

neverthrowとoption-tを比較した場合の話

必要だと思ったものを継ぎ足してるんで、いつ比較したのかの時期に依存すると思うんだけど、ここでは

を底として話す。

誤解1: async周りの対応がoption-tには無い

./docs/public_api_list.mdAsyncって名前の入ってるやつがそう。

誤解2: try-catchを Result 型に変換する仕組みがoption-tには無い

あります。今までは各ユーザープロジェクト側で必要に応じて実装してたけど、流石に毎回再実装するのがだるいので2022年の夏頃に実装した。

neverthrowのfromPromisefromThrowable と関数のシグニチャが違うのは設計の前提となる洞察の差異だと思っている。

Option-t側に実装するに際して、「try-catchから Result 型をJS(TS)で作りたい状況とは如何なるか?」を検討した結果、自分は以下の2ケースだと判断した。

  • なんでもいいからtry-catchをResult<T, unknown>に変換したい場合
  • catch節で掴んだものがErrorであると何かしらの保証を加えたい場合
    • つまり、 stringとかnumberが`throwされているのは流石に弾きたいような場合

Result<T, unknown>を具体的なResult<T, E>に変換したいならば mapErr()を使って変換すればいいしね。

neverthrowはそれの組み合わせを結合した結果、現在のAPIシグニチャになっているのだろうと思う。

誤解ではないが弁護したい: option-tはドキュメントが少ない

これはかなり意図してそうなっている。

まず、そもそもとして冒頭でも述べたように、自分は実際には各プロジェクトのmotivationに依存して最終的なconvention(規約)が決定されるのであって、ライブラリそのものに強い色をつけるのが妥当と思えず、この種の一般的なデータ構造の実装を提供するライブラリそのものはデータ構造の提供に止めるべきであろう、ましてエラーハンドリングのようにコードの設計や運用指針まで影響するものであれば尚更、という設計意図が非常に強い。

第二に、ドキュメントを書いてもメンテナンスしきれない。仕事でも使うけど実質趣味活なので、メンテナンス性の観点で割り切っている。

第三に、Rustのstd::resultstd::optionと類似のAPIのJS版実装からプロジェクトが始まったので、わざわざ自分でドキュメントを用意する必然性が薄い。

第四に、各関数単位ではvscodeなどのインラインガイド用にJSDocコメントを大量に書いているのと、実装自体がいずれも単純かつ極小(数行)なので、「困ったら実装をコードジャンプして参照してくれ」という感じ。

ただ、流石に何もなさすぎると言われればそうかもしれないので、オペレータ関数の逆引きみたいなものは作ってもいいかなと思っている。

貧弱だけど既にあった。自分で追加したのを忘れていた....

neverthrowと比較した場合の設計意図の違いについて

ざっと見た場合、以下が設計方針として違うかなと思う。

  • documentation(上で述べた通り)
  • メソッドチェーンの有無
    • classベースの実装か否か
    • tree shakingのサポート
  • Result 型以外の実装も提供している

option-tはメソッドチェーン記法を明確に捨てている。

実際これは結構意見が分かれるところだと思っているし、初期はメソッドチェーン版を実装していた(現在も非推奨ながら後方互換と好みの問題で残してある)。

捨てた理由としては、まさにtree shakingの可否すなわちバンドルサイズへの影響。

一般的なアプリケーションから、配布サイズにセンシティブなSDKや配信ウィジェットでの利用まで含めて実用的にしようと思うと、この選択となった。

Result 型に対するオペレータ関数は実質無限に近しいパターンを実装可能であり、頻出パターンはライブラリとして提供するのが望ましい一方、それら全てが常に使われるわけでは無いという問題と隣り合わせである。

JavaScriptの場合、class表記でも単純なオブジェクトでも、プロトタイプチェーン上に乗っかってしまった未使用プロパティ(メソッド)の安全な削除は非常に困難。プログラム全体を俯瞰して使用されているか否かが静的に決定できない場合、未使用プロパティメソッドといえども削除できない。仮にprototype chainを全て舐めるリフレクション的な操作が入ってきた瞬間に不可能になる。

これをサポートしているツールチェインも、例外的にGoogle Closure Compilerのadvanced optimization mode程度で、現代においては決してメジャーかつ一般的な選択肢では無いので、設計上の選択肢から外れた。

他にも当時(2017年とか?)のTypeScriptの型推論機構と組み合わせた場合に、メソッドチェーンの途中で推論が諦められてジェネリクスの型が anyにすぐ落ちてしまうので明示的な注釈が必須でダルくてしかたなかったとか、semverに基づくとプロジェクトの依存関係にmajor version違いの同名パッケージが複数個存在することがあってclassベースの実装だとinstanceofで非常に直感に反する挙動となるケースがあるとか、色々あって、メソッドチェーンベースの記法を諦めて、冗長ではあるが、通常の関数呼び出しの連鎖に変更し、tree shakingの完全なサポートに舵を切った。

pipeline operatorが実現すればoption-tでももう少し書きやすくなるとは思うけれども、結局プログラムは書く瞬間よりも読まれる瞬間の方が圧倒的に長く、極端にダルい・変な書き方でない限りは愚直に書けば十分だろうと自分は強く考えているので、もっと書きやすくしたいが、これはこれでいいと思っている。

Result 型以外の実装も提供しているのは、歴史的経緯というか、もともと Option 型の提供からスタートして、その後、 Result 型や関連するデータ型に関して、1パッケージで自分の需要を全て満たそうとしたらこうなった感じ。あんまり深い理由はない。

neverthrowと共通しそうだなと思っているところ

これも勝手な推測なのだけど、オペレータ関数周りは、どちらのライブラリも

  1. 本当に基礎的なものは提供する
  2. それを組み合わせて作るドメイン特化的なものは、各ユーザープロジェクトで自由に実装してくれ
  3. よほど便利なものはライブラリ側で取り込む
    • こうするとライブラリ側固有の最適化を行える場合がある

のは共通してそう

まとめ

好きなの使えばいいよ

おまけ: 例外といえば好きなミーム

Viceroyを眺める

便利そうなプレスリリースが出て、色々調べてたら開発用ローカルサーバーのコードが見つかったので土曜の夜の暇つぶしに眺めてみる。

開発用ローカルサーバーのコードを読んだところでなんだという感じなのだが....

登場人物

概念

  • downstream request/response: クライアントから飛んできたリクエスト/レスポンス
  • upstream request/response: バックエンド系に飛んでいくリクエスト/レスポンス

ゲスト側

fastly create

Fastly Compute@EdgeSDKリポジトリは非公開のようだがcrate.ioやdocs.rsでホストされており、コード自体は見れる。

fastly-sys

上述のfastly createが呼び出すFFIextern "C"で定義. 要はユーザーの書いたwasmモジュールにimportされる関数のシグニチャを定義しているにすぎない。基本なんでもハンドルを経由して動かす。

ホスト側(Viceroy)

MozillaからFastlyに移籍したWasm/Rust組がIssueとかでよく見かける(当たり前)。

Viceroyrev. 28e4078e時点)

fastly CLIfastly compute serveすると起動する「開発用ローカルサーバー」の実態

今のところ未実装なものが色々あるが、読んで遊ぶには十分。

Fastly社の各種発表を聞くに、商用環境のホスト側はlucetでwasmモジュールAOTコンパイルした上で実行しているらしい。一方、こちらはwasmtimeを使っているのでサーバー起動時にwasmtime::Moduleを生成、各リスエストごとに実行時に環境(後述のSessionとリンクしてwasmtime::Instanceを作って実行する。

アプリケーション自体はhyperを使って書かれているのでいろいろasync/awaitする.

compute-at-edge-abi

witx(初めて知った)で記述されたABI定義。fastly-sysに対応する定義が記載されている。

Viceroyではwiggleの提供するprocedural macroを用いて、viceroy_lib::wiggle_abiとの対応を取る

viceroy_lib::session::viceroy_lib

downstream requestごとに生成される. 上述のcompute-at-edge-abiで定義されたABIもほぼこの上に実装されている.

処理の流れ

作るのがめんどくさいのでシーケンス図は省略

一番美味しいところはExecuteCtx::run_guest()を読めばよい.

各downstream requestに対するゲストコード(wasmコード)は、tokio::task::spawn()される。デスヨネー。

SessiontokioのchannelのSenderを持っているので、downstream responseが打ち返されたら(fastly_sys::fastly_http_resp::send_downstreamが呼び出されたら)、渡ってきたSenderに詰める.

つまり、ゲストコードがfastly::Response::send_to_clientfastly::ResponseHandle::stream_to_client )やfastly::Response::stream_to_clientfastly::ResponseHandle::stream_to_client )を(ユーザーまたは#[fastly::main]によって生成されたコードが)呼び出すことで、処理がホスト側に渡る。

もちろんホスト側ではそれを待ち構えているので、うまいことハンドルして終わり。

感想

小さいしRustで書かれてるので読みやすい。Proxy-Wasmな、Wasmな任意コードを動かす配信サーバーの実装例 + ユーザー向けSDKの作り方としても読める感じだった。

ただFastlyというCDNサービスを使う側の視点としてViceroyを見た場合、あくまでもFastly Compute@Edgeのユーザー用ローカル開発シミュレータ専用に実装されたようにも読めるので、本番との挙動の差異をどうやって埋めていくかが気になるところ。テストスイートとかどうするんだろう?

謝辞: #web24 のperformanceセッションありがとうございました

書こう書こうと思っていたらすっかり7月になってしまいましたが、以前、宣伝をしたWeb 24のPerfomanceセッションの観客の皆様、ならびに共演者各位ありがとうございました。

saneyukis.hatenablog.com

....楽しんでもらえたのかな?

登壇者の自分個人としては、「ああ、もっとプロレスに持ち込めれば良かったなあ」とか「もっと共演者を挑発してコンテンツ性を高める制圧力があればなあ」と反省点ばかりです。

また、内容についてはハイコンテクスチュアルに成らざるを得ないのはともかくとしてWebの話をしすぎたのがちょっと悔しい。 ソフトウェアパフォーマンスってWeb固有の問題ではなく.... そもそもWebってなんだよという話なんですが、ソフトウェア工学的な話の中にWeb固有の事情があるという位置付けだと思っているものの、そういう方向に話を持っていけなかった自分の制圧力の無さにちょっと後悔している。

言い訳をする気になれば「とはいえ話のフォーカスを作らないといけない」とか色々できるんですが、自分の話術の未熟さの方が反省点として勝るので、ここらへんでやめておきます。

楽しんでいただけならありがとうございました。

また次回、何かでご縁がありましたら、その時はまたよろしく。

宣伝: #web24 のperformanceセッションに出演します

セッションオーナーのnodagutiさんにお声がけいただいて、Web 24というオンラインイベントのperformanceセッション(5/8(土)の11:10~12:40)に出演することになりました。

なんとか24….. 24時間その話をする...... うっ、頭が......

普段はこういう宣伝はあんまり書かないんですが(書いてもTwitterでチョロっと書くだけだったりなのですが)、主宰筆頭のJxckさん曰く「出演者込みで熱量を作っていきたい(要約)」ということで、確かにそういうのって大切なことで、出演者が盛り上げなくて誰が盛り上げるんだと珍しくブログに書くべく筆をとった次第となります。

イベントについて

さらなる詳細や共演者各位の紹介、ハッシュタグについてはイベント詳細ページをご覧ください。 僕が下手に説明するよりも、そちらを参照する方が間違いがないでしょう。

完全ぶっつけ本番のイベントで、セッション開始直後の出演者紹介タイムとイベント全体の趣旨の説明以外全く打ち合わせしていないガチンコプロレスとなります。「セッション中の 休憩の為の 参加者アンケートタイムなどやらんでよし!全力で殴り合え!議論しなければ生き残れない!」タイプのイベントになるらしいです。

しょうがないので議論で負けそうになった時の反撃用に一斗缶を用意しようかとも思ったのですが、これはオンライン配信イベントであり、画面の向こうに投げることができないことに気づき断念しました(これは番組上の演出であり、当日はCode of Conductに則り安全にイベントが進行されます)。

あと、「スパチャで限界スパチャ芸をやりたいんだけど無いの?」との質問を友人から頂いたのですが、本イベントはそういうのはやらないとのことなので、どうしても課金したい方(いるかはわからないけど)は、応援したい団体・プロジェクトやお店への寄付または課金で代用してください。

出演経緯

さてさて。

上に書いたような電流デスマッチイベントなわけですが、とりあえず自分を呼んでくださった理由をセッションオーナーのnodagutiさんに軽く聞いたところ「小手先の”パフォーマンス改善テクニック”にとらわれない,本質的な改善を志向されていた印象がありまして」との返答をいただきました。

他の皆さんはさておき、このふわっとした感じ、これは僕に限っては専門家として呼ばれたんじゃなくて、熱意 + プロレス適正の高さ + ちょうどいいところにGW暇そうな人間がいたからで呼ばれたってヤツなんじゃないか? きっとそうに違いない。とりあえず精一杯バックドロップをかけられないかタイミングを狙っていきたいと思います。オラッ!見せ物だぞ!

いやー、過去に色々登壇したりしたからその実績だと思いたい。思いたかった。仕方ない。仕方がないので凶器になるパイプ椅子を探そう。

あと、自分はこの手のオンライン配信イベントは初参加で不慣れなので、とりあえずプロに学べということで声優の出るラジオやYouTubeの番組をいくつか視聴して整えています。とりあえず番組固有の挨拶を考えないとダメなのかな。

よろしくお願いします

そんなこんなで、当日は頑張ってやっていくのでよろしくお願いします。お暇がありましたら覗いていただければ幸いです。

ちなみに これが一番重要なんですが、アーカイブ配信は無いらしいので、一期一会のライブ感をお楽しみください

Reactのprops/contextの使い分け

Reactのprops/contextの使い分け

仕事先でたまたまこれの話になり、個人的に思っていることをまとめた。 公開したのは、時々見かける「どっちを使うべき?」みたいな議論に 自分も混ざりたかった 思うところがあったから.

「とにかくpropsでいい」と自分は考えている。

なによりReactは書き方に詰まった場合に、フレームワークライブラリ固有の事情を考慮して解決するというよりも、実装や設計上の問題が一般的なプログラミングパターンの範疇の発想で解決できるのがよい

前提

以下のように考える

  • React/preact のコンポーネント = 通常のclassや関数
    • 状態を隠蔽して抽象する
    • 最近は冪等性がどうとかReact語るときにあんまりいわなくなったけども....
  • props = 関数やメソッドの引数(入力)
  • context = グローバル変数(モジュールグローバルな変数)

実装の指針

1. まず引数(props)でどうにかできないか考える

  • グローバル変数に依存しないクラス・関数の方が、テストもメンテナンスしやすい
  • 大抵の場合はpropsでどうにかなる

2. propsでは「めんどうくさい」「うまくいかない」と感じた時にグローバル変数(context)を、やむを得ず使う

以下のようなケースで、グローバル変数を使った方が良いのか一回落ち着いて考えた上で、それでもやっぱりグローバル変数(context)を使った方が良いなと思ったら使う

  • スケジュールの都合、propsよりもcontextを用いた方が簡単である
  • ページ(アプリケーション)中で同じ値を、必要な箇所で参照している
    • テーマ機能の実現
    • ページの初期化時に取得した認証情報を触る
  • propsで渡すと、どうにもコードが簡潔に書けない

注意点

  1. Contextを利用する場合, <Context.Provider/>で囲った箇所(ツリーの子孫)は、Contextの値が更新されると全て再実行される。そのため、性能問題が発生しやすいので、値が頻繁に更新されるものは、上の基準に合致していても、なるべくpropsで渡すようにする
    • そもそも自分はなるべく使わないのでそんなに困らんのだけどね.....
  2. Contextを利用する場合であっても、なるべくuseContextの呼び出し回数を最小限にする

コードレビューの指針

以下を見る:

  1. そのcontextの影響範囲・利用用途は適切か
    • 影響範囲が広い場合、注意深く検討する
  2. 気になるなら実装者に「propsに直してみるのはどうだろう?」と聞いてみる
    • 実装者に任せてみる
  3. 究極的には「どんなに”ひどい”設計・実装でも、影響範囲が小さい(使用箇所が限定されている)ならば、大抵は問題ない」ということを留意する
    • 困ったら、その時にリファクタしたり、丸々書き直せば良い
    • あとで修正しやすくするために、テストケースの有無や妥当性を検討する方がよい

Q&A

Q. propsでなるべく渡そうとすると、どうしてもpropsで受け取る値が増えてしまうけど、どうすればいい?

<Hello bar1 bar2 bar3 bar4 bar5 bar6 bar7 bar8 bar9/>

のように、たくさんの値が必要になってしまう場合. 以下のようなアプローチが考えられる

  • 「ひとつのコンポーネント(関数)で多くのことをやろうとしすぎている」ことが多いので、困難を分割する
  • 関連する値をオブジェクトに一度まとめて渡す
    • ValueObjectを作る....というか一般的なモデリングの話というか
  • render propsprops.chidlrenといった手法を試す

背景

そもそも自分はContextを使わない。

  • ライブラリや他人の書いたコードがそれを要求してくる
  • 設計上とスケジュールの制約上、どうしてもContextを用いた方が早く作れる
    • かつ、値がほとんど更新されない

場合くらいしか使うことも使った記憶もなく、自分で設計を決められる場合は

  • 全体に影響がある値は、ルートにStoreないしStateを用意する
    • 余裕があればgetter setterを別のインターフェースとして定義する
  • そしてそれをひたすらにバケツリレーする

スタイルでゴリゴリやってしまう。

グローバル変数のたとえもあるし、「選択という手間を省き、その分の思考のリソースをより根本的な設計を検討するのに注ぎ込みたい」意味もある。 また、常にpropsを用いることで、パフォーマンスやリファクタの問題をシンプルにする、という意味合いもある。

あと、自分がReactやWebクライアントサイドアプリケーションに関して一番熱心に考えていた頃はLegacy Context APIしかなかった結果が、このスタイルに自分を規定した気はする。あの頃の「機能としては存在するけどなるべく使うな」の趣を思い出せば、自然と基本はprops一択になる。

対して。現在のContext APIはだいぶよくできている。 しかしながら、グローバル変数の例えもあるし自分からは積極的に使うほどでもない、みたいな距離感。

そんな感じなので、正直、「Contextで困ったらプロファイラ使って測って遅いところ削ったり、必要に応じて抽象化のリファクタすりゃいいじゃん。別にReact専用の体系的なあれこれ要らないよHAHAHA」程度の気持ちだったところ、ケーススタディガイドラインとしては以下のような切り口もあると嬉しいのではないかと同僚からフィードバックをもらった。

たとえば

  • 巨大なContextは作るな分割しろ
  • 直接 useContext()は使うな、カスタムフックの裏に隠して最適化の余地を残せ

みたいなのあるじゃないですか、と。

そう言われると、ReactのContextで困るとそういう感じのことやることになるよなあ、てか職場を問わず毎年1回〜2回はそういう変更のコミット作ってたわ。確かにね。

ただ、それらは自分にとっての体系的な何かというよりは、一般的なプログラミングのグッドプラクティスや技法の域を出ず、React固有の何かとして特筆する気になれないのであった(というわけで、ガイドラインのContextベストプラクティスの章は彼に任せることにした)。

IE11とその他ブラウザの最新版ってどのくらいベンチスコアに差があるのか(手抜き版)

IE11のリリースは2013年秋。それから7年間で競合がつっこんだ投資の結果、ベンチマークスコアに差はどのくらい生まれているのかを知りたかった

注意点

  • EdgeHTMLを実行できる古いWindows環境は手元にないので無視する
  • Microsoft Edge (Blink)はどうせBlinkでしょ….ということで無視する
    • Edge限定のパスとかMSが用意してないことに掛ける
  • もはや大体のベンチマークは動かないので、ノー修正で動くものだけトライする
    • 結果, JSとDOMだけの計測に
    • たぶんpolyfill入れたりbabel通せば動くとは思うが、とりあえず今回はスキップする
      • MotionMarkは動かしたかったが…..
    • そのうち真面目に色々動くようにした上で再計測にしたやつをやるかも

環境

  • Windows 10 20H2
  • Dell XPS 13 (2015)
    • CPU: Intel Core i7 5500U 2.4GHz(物理2コア4スレッド)
    • RAM: 8GB
    • GPU: Intel HD Graphics 5500
  • ハイエンドではないけど、まだまだローエンドってわけでもない、はず

スコア

JetStream 1.1

IE11 Firefox 85.0 Chrome 88.0.4324.146
Latency 74.477±18.065 75.946±22.499 47.194±22.116
Throughput 69.304±4.1786 144.56±15.191 191.58±125.61
Geometric Mean 71.482±9.9780 109.16±20.701 103.91±58.452

Speedometer 1.0

IE11 Firefox 85.0 Chrome 88.0.4324.146
Score 23.6±1.9 89.9±2.8 125.4±0.81

いろいろあった

I'll leave the current job at the end of December. The reason is a difference of a direction of my musicality from the company's one.

Of course, there were bunch of things. I can talk about their details but I seem it would not be nice case study. I can only say the difference of the direction. I think everything was caused by it.

Next year....... I'm planning to start a work as a freelance. I know well that this is not a best direction for me. However, I don't have any other choice. One thing I can say for sure is that I will not have a plan to work as a freelance for many years - ideally I'd like to finish and join to a nice company in several month~1 year.

Happy holidays. Have a happy new year.