Build. Translate. Understand.

パイプ&フィルターを用いた曲レコメンデーション

2025-07-16 21:00:25

原文: Song recommendations with pipes and filters by Mark Seemann

小さな Recawr サンドイッチを構成する

この記事は、関数型プログラミングによる設計の代替アプローチを解説するシリーズの一部です。シリーズの最初の記事では目次や全体の構成、そして使われている継続的なサンプルについて説明しています。簡単に言えば、このサンプルは大規模なデータセットを対象とした曲レコメンデーションエンジンです。

このシリーズの以前の記事では、純粋関数へのリファクタリングや、副作用のある処理のコンビネータによる合成について取り上げました。今後数回の記事では、メッセージベースのアーキテクチャを使ってアルゴリズムをより小さなRecawr サンドイッチに分割する方法を見ていきます。このようなアーキテクチャは全体として「パイプ&フィルター」と呼ぶことができるでしょう。この種の分割・構成に関しては、書籍『Enterprise Integration Patterns』が優れた参考資料です。書名に惑わされないでください。実際のところ「エンタープライズ向け」でも「インテグレーション」に特化した内容でもありません。

小さなサンドイッチによるワークフロー

Impureim Sandwichにした曲レコメンデーションで述べたように、ユーザーに対して曲を推薦する際に用いるデータセットが非常に大規模である可能性があります。そのため、Impureim サンドイッチという手法は現実的でないかもしれません。

一方で、メッセージベースのシステムは基本的に、多くの小さなメッセージハンドラで構成されており、それぞれが Impureim サンドイッチとして実装可能です。このような分解についてはすでに純粋なインタラクションという記事で簡単に触れています。ここでは、各メッセージハンドラ(またはアクター)が、Webリクエストのハンドラ(Controller)などと同等の小さなプロセスであると説明しています。

曲レコメンデーションの問題に関して言えば、小さな「フィルター」を使って別の処理を始めさせ、それをさらに別のフィルターが扱う、というような小規模なパイプ&フィルターアーキテクチャを検討できます。

三つのボックスが左から右へ矢印でつながり、メッセージの流れを示している図

実装の詳細によって、この手法を「パイプ&フィルター」「リアクティブ関数型プログラミング」「アクターモデル」「Map/Reduce」などと呼ぶこともできます。ここで重要なのは、それぞれの「フィルター」が小さな Recawr サンドイッチであるという点です。これは、外部の音楽データサービスに対して問い合わせを行い、得られたデータに純粋関数を適用し、最終的に別の副作用のあるチャネルにメッセージを送る、という構成です。次回以降の記事では、そのコード例を紹介していきます。

  • C#のReactive Extensionsを用いた曲レコメンデーション
  • F#のエージェントを用いた曲レコメンデーション

この記事執筆時点では、Haskell の例を紹介する予定はありません。主な理由は、Haskell で非同期のメッセージベースシステムを書いた経験が私にはないからです。簡単な検索では関数型リアクティブプログラミング用のライブラリが数多く存在することは分かりましたが、一方でメッセージバスを使った非同期メッセージングに関する情報はほとんど見つかりませんでした。

むしろ、Erlang の例を出す方が言語としては適切かもしれませんが、最後に Erlang を独学で学ぼうとしたのは随分前のことで、習熟するには至りませんでした。

モデリング

曲レコメンデーションの問題を、各フィルターが Recawr サンドイッチとなるように細かく分割していくと、コンビネータを用いた曲レコメンデーションで見た構成と非常に似通ってくることがわかります。 また、Oleksii Holub 氏が述べているように

「一つのまとまった要素として理解可能なものが、意味や価値を持たない複数の断片に分かれてしまった。個別のユニットテストは容易になったかもしれないが、アルゴリズム全体の正しさを保証するにはあまり役に立たない。」

これは今回のケースでも同様です。では、なぜわざわざこのような分割を行うのでしょうか?

このシリーズの目的は、Scott Wlaschin 氏の優れた記事 タートルを見る13の方法 と同様に、設計のさまざまな選択肢を提示することにあります。たとえある設計手法がこの曲レコメンデーションの問題にとって最適でなくても、別の問題に対しては適している可能性があります。そのときに、このシリーズから応用方法を導き出せることが大切なのです。

結論

この曲レコメンデーションの元になっているコードが、実際に約10分間も動作することが知られている以上、進捗表示やキャンセルが可能な非同期処理を導入する意義は十分にあります。そしてこれは、自ずとパイプ&フィルター型のアーキテクチャに行き着きます。

次回の記事で紹介するように、メモリ内のメッセージストリームを用いてこのアーキテクチャを構成することもできますし、永続化されたメッセージバスと分散・再起動可能なメッセージハンドラを備えた、本格的なメッセージングシステムを使うことも可能です。実装の詳細には違いがあるものの、基本的なアーキテクチャの考え方は同じです。

次回: C#のReactive Extensionsを用いた曲レコメンデーション


インデックスへ戻る