Impureimサンドイッチ
2025-04-22 12:30:25
原文: Impureim sandwich by Mark Seemann
「インピュリアム・サンドイッチ」と発音します。
2017年1月以来、私は 不純/純粋/不純 サンドイッチを称賛してきましたが、この用語を定義する記事を公開したことはありませんでした。本稿では、それを補いたいと思います。
関数型アーキテクチャ
関数型アーキテクチャでは、純粋関数は不純な処理を呼び出すことができません。一方で、Simon Peyton Jones 氏が講演で述べたように、純粋な計算の結果を観測すること自体が副作用 です。実際のところ、純粋関数の 実行 も非決定的に発生するため、不純とみなされます。つまり、関数型スタイルで書かれたソフトウェアであっても、エントリポイントは不純でなければなりません。
純粋関数が不純な処理を呼び出せないのに対して、逆は禁止されていません。不純な処理は 純粋関数を呼び出すことができます。
したがって、私たちが目指せる最良の形は、不純なエントリポイントから純粋なコードを呼び出し、その純粋関数の結果を不純に報告する、というものになります。
ここではコードの流れは上から下に進みます:
- 不純な情報源からデータを収集する
- そのデータを使って純粋関数を呼び出す
- 純粋関数の戻り値に基づいて状態(ユーザーインターフェースなど)を変更する
これが 不純/純粋/不純 サンドイッチです。
メタファー
これを サンドイッチ と呼ぶ理由は、私にはその見た目が本当にサンドイッチのように思えるからです。やや背が高いかもしれませんが。サンドイッチの起源にまつわる伝説によれば、第4代サンドウィッチ伯爵 は大のギャンブル好きで、カードゲーム中に手を汚さずに食べられるよう、肉を挟んだパンを注文していたそうです。仲間たちは「サンドウィッチと同じものを」と注文するようになり、それがそのまま料理名として定着したのです。
私はこのサンドイッチという比喩が気に入っています。パンは Donald A. Norman 氏の言うところの アフォーダンス(行動を可能にするもの) であり、肉を手で持っても指を油まみれにせずにすみます。同様に、不純な処理は純粋関数を「扱う」ことを可能にします。すなわち、純粋関数を呼び出し、その結果を観測することができるのです。
例
Impureimサンドイッチ の最もわかりやすい例の一つは、私の元々の記事にあります:
tryAcceptComposition :: Reservation -> IO (Maybe Int) tryAcceptComposition reservation = runMaybeT $ liftIO (DB.readReservations connectionString $ date reservation) >>= MaybeT . return . flip (tryAccept 10) reservation >>= liftIO . DB.createReservation connectionString
この記事では、コードを再掲しつつ、それぞれ不純・純粋・不純の部分に背景色を付けて示しています。
最近では、F# を使って 登録フローを関数型アーキテクチャへリファクタリング する中でも、このサンドイッチ構造の例を多く示してきました:
let sut pid r = async { let! validityOfProof = AsyncOption.traverse (twoFA.VerifyProof r.Mobile) pid let decision = completeRegistrationWorkflow r validityOfProof return! decision |> AsyncResult.traverseBoth db.CompleteRegistration twoFA.CreateProof |> AsyncResult.cata (fun () -> RegistrationCompleted) ProofRequired }
この最後の例では、サンドイッチの下部が他の部分よりも大きく見えるかもしれません。こういったことは時々起こります(実際、最後の行も純粋です)。一方で、中央の純粋な部分は、たとえ呼び出している関数の中で複雑な処理が行われていても、見た目にはたった1行のコードに見えることが多いものです。
このサンドイッチは言語に依存しないパターンです。C# にも適用できます:
public async Task<IActionResult> Post(Reservation reservation) { return await Repository.ReadReservations(reservation.Date) .Select(rs => maîtreD.TryAccept(rs, reservation)) .SelectMany(m => m.Traverse(Repository.Create)) .Match(InternalServerError("Table unavailable"), Ok); }
前の F# の例と同様に、最後の Match
はおそらく純粋です。実際には、InternalServerError
や Ok
のようなメソッドが基底クラスから継承されている場合もあり、純粋かどうかを見分けるのが難しい こともあります。とはいえ、アーキテクチャ上の観点ではそれほど重要ではありません。なぜなら、その部分は処理としては単純だからです。
名前について
この比喩を思いついて以来、より良い名称を探してきました。不純/純粋/不純サンドイッチ という呼び方は少々煩雑に思えますが、それでも多くの人がこの名前を使ってくれているようです。
もっと特徴的な名前にしたいと思い、impure や pure のさまざまな略称を考えてきましたが、最終的に Impureimサンドイッチ に落ち着きました。これは impure/pure/impure を縮めた形です。
なぜこの略し方にしたのか?
これまでいろいろな案を試しました:
- impureim: impure/pure/impure
- ipi: impure/pure/impure
- impi: impure/pure/impure
- impim: impure/pure/impure
…などなど。
私は impureim が気に入っています。なぜなら、私が知っている限り、唯一のアナグラムが imperium(インペリアム=帝国)だからです。したがって、発音は インピュリアム・サンドイッチ を推奨します。これは新造語であり、シボレス(仲間内の合言葉)としても機能するでしょう。
まとめ
関数型アーキテクチャでは、純粋関数が不純な処理を呼び出すことはできません。一方で、結果を観測できなければ純粋関数は無意味です。したがって関数型アーキテクチャでは、純粋関数を呼び出し、その結果に対して別の不純な処理を行う不純なエントリポイントが必要不可欠です。
このような 不純/純粋/不純 の相互作用を、私は Impureimサンドイッチ と呼ぶことを提案します。 インピュリアム・サンドイッチ と発音しましょう。
追記(2025年1月18日):
この概念をさらに掘り下げた後続の記事もご覧ください:
これらの記事には、さらなる例やよくある質問への回答が掲載されています。