TypeScriptでつくる階層型Pipeline構造 ― 安全な関数連結と注入
経緯
クリーンアーキテクチャのやつを作ったときのPipelineが、だいぶ使いやすそうだったから
色々使い回せるように作り直したかった。
中身の話
全体的に、前回のやつのPipelineブラッシュアップ版
自由に関数が繋げて、前の関数の戻り値型が、そのまま次の関数のinputに変わるので
繋げてて幸せになれる。
途中でErrorを返すと、そのままChainが止まるようになっているので、エラー処理が楽かも。
fromの時点でINの型を与えないと、推論できなくなっちゃうので、与えてる。
この方は、runの引数につながるので、実行時に指定する型を間違えなくていい感じ。
実際は、無名関数ってよりは、普通の関数をつなぐので、型自体は指定されてることが多いかなと思う。
どっかでヌケモレが出てそうなんだよなぁ・・・。
あと、ループ時点ではanyを使わざるを得ない(実行時には、コロコロと型が変化する)ので
いっそ、オブジェクト同士をChainさせたほうがとは思ったけど、そんなにクラス作りまくるのもなぁ
と思ったので、今回はコレぐらいで・・・
Stepを配列にしないでChainにしてあげれば良いのかもしれないし
まぁこれはコレで動きは早そうだし良いのかなぁ。
余談
AIにどう?ってきくと、いっぱい褒めてくれるから嬉しい!
コード
解説
ResultType
戻り値で成否を扱いたかったので、Errorかそれ以外(ロー◯ンドみたいな)感じにした
makeStep
こいつは、Pipelineに関数を仕込むときに、あまりに分かりづらいので切り出した部分。
レイヤの処理が行われたときの戻り値をある程度安全に処理できるようにラップしたクロージャが返る
Pipeline
だいぶ前回よりスッキリさせた。
前のOUTを次のINに繋ぎこんでいく本体処理
説明を書くのがあれだったので、AIさんにお願いして出してもらった
| メソッド | 型定義 / シグネチャ | 役割・説明 |
|---|---|---|
static from<Init, R>(entry: (input: Init) => Result<R>) |
→ Pipeline<R, Init> |
パイプラインの起点を作る。最初の処理(エントリ関数)を登録し、Pipeline インスタンスを生成する。すべての流れはここから始まる。 |
then<R>(fn: (input: I) => Result<R>) |
→ Pipeline<R, Init> |
前の処理の出力を次の処理に渡す。 関数をチェインして新しいパイプラインを返す。 ( Promise.then に似た動作) |
inject<R>(pipeline: Pipeline<R, I>) |
→ Pipeline<R, Init> |
別の Pipeline を途中に挿入する。内部的には .run() を呼び出して結果を引き継ぐ。サブルーチン・モジュール的な再利用に使える。 |
tap(fn: (arg: I) => void) |
→ Pipeline<I, Init> |
データの流れを変えずに副作用処理を行う。 ログ出力やデバッグ用など。結果は次の処理にそのまま渡される。 |
run(input: Init) |
→ Result<I> |
パイプラインを実行する。 各 step を順に呼び出し、途中で Error が発生した場合は即座に終了。成功時は最終出力を返す。 |
injectが面白くて、レイヤの途中に、pipelineが挟み込める仕組み。
新しいレイヤとして、pipelineを実行する処理を挟み込むって仕組みになっているので、構造の整理に色々使えそう。
途中でErrorを返せば、即時レイヤ処理は止まるようになっていて、Array.forEach止まらない系の悲しみを背負わずにすむ。
実行部
前と使い方はそんなに変わらない。中身がスッキリしたのと、injectの使い方が変わったかなぐらい。
Discussion