関数型プログラミングによる設計の代替アプローチ
2025-04-10 19:00:25
原文: Alternative ways to design with functional programming by Mark Seemann
サンプル問題に対する関数型プログラミング(FP)による解決策カタログ
プログラミング学習において初心者段階を卒業された方なら、特定の問題を解決する方法は、通常一つではないことにお気づきでしょう。ある方法が他の方法より優れていることもありますが、多くの場合、明らかにこれが一番、という選択肢は存在しません。
「適材適所」や「目的に合った道具を使う」というのはよく言われることです。プログラマにとって最も重要な「道具」とは、使うIDE(統合開発環境)そのものよりも、我々が習得している技術、パターン、アルゴリズム、データ構造のことです。そして、目的に合った道具を選ぶためには、そもそも複数の選択肢を持っていなければなりません。これもまた、プログラマにとっては、問題解決の方法を複数知っておく必要がある、ということを意味します。これが、私がコーディング・カタ(練習問題)を行うことに価値を見出す理由です。
この記事から始まる連載では、コーディング・カタの代わりに、一つの例題を取り上げ、それに対する複数の代替的な解決策を順に提案していきます。ここで取り上げる問題は典型的なカタよりも規模が大きいですが、この連載を、Scott Wlaschin氏による『タートルを見る13の方法』の姉妹編のようなものだと考えていただければと思います。
「曲のレコメンデーション」問題について
私が今回取り組む問題は、2020年にOleksii Holub氏が提示したもので、提示されて以来、どのようにアプローチすべきかずっと考えてきました。Holub氏は、『Pure-Impure Segregation Principle(純粋・不純分離原則)』と題された記事の中で、2つの例のうちの2番目の例としてこの問題を紹介しています。かいつまんで言えば、この問題は、膨大な「スクロブル」(再生履歴)のリポジトリを元に、特定のユーザーへのおすすめ曲を特定する、というものです。記事中の最初のコード例も良いものですが、今回の目的においては、掘り下げるべき問題点がそれほど多くないため、ここでは触れません。
Holub氏の記事では、私の書いた記事『Dependency rejection(依存関係の排除)』や『Impureimサンドイッチ』パターンについても言及されています。私の経験上、Impureim Sandwichパターンは、一見すると明らかな制約があるにもかかわらず、驚くほど多くの場面で適用可能です。「このパターンは一般的には使えない」という反応をいただくことも一度や二度ではありません。私自身、Impureim Sandwichが万能な解決策だと主張したことはありません。ただ、問題に少し手を加えることで、驚くほど多くの場合にうまく適合する、と言っているのです。具体例としては、以下のようなものがあります。
- 登録フローを関数型アーキテクチャへリファクタリングする
- 条件分岐を含むサンドイッチの例
- 関数型ファイルシステム
- レストランにおけるサンドイッチの例
- C#によるImpureim Sandwichとしての曲レコメンデーション
- F#によるImpureim Sandwichとしての曲レコメンデーション
- HaskellによるImpureim Sandwichとしての曲レコメンデーション
一方で、私はこのパターンではうまく扱えないような例を募集しており、読者の方々から時折例を提供していただけることに感謝しています。Impureim Sandwichパターンが実際にどの程度広く適用できるのかを見極めようとしており、その限界を知ることは、この取り組みの重要な一部です。今回取り上げる曲のレコメンデーション問題は、これまで私が見てきた中で最も扱いにくい例の一つであり、この例を提供してくれたOleksii Holub氏に感謝しています。この連載記事では、この問題に対する様々な代替ソリューションを提示していきます。誤解のないように明確にしておきたいのですが、私がこれを行うのは、元の記事で示されているコードが悪いと考えているからではありません。むしろ逆で、もし自分で書くとしても、おそらく同じように書くでしょう。
私が代替案を示すのは、教育的な目的のためです。問題解決の方法を複数知っていてこそ、目的に合った適切な「道具」を選ぶことができるのです。最終的に、Oleksii Holub氏がすでに提案している設計が最適だと判明するかもしれません。しかし、状況が変われば、別の設計の方が優れている可能性もあります。究極的には、読者の皆さんが、この問題から学びを得て、自身が遭遇するかもしれない他の問題に応用できるようになることを願っています。
今日のネット上の多くの議論のあり方を考えると、以下の点を強調せざるを得ないのは残念ですが、念のため:Oleksii Holub氏の記事を、私の提唱する関数型アーキテクチャやImpureim Sandwichに対する反論だと解釈する人もいるかもしれません。私はそのようには捉えておらず、むしろ知的な探求を誠実に行う試みだと考えています。私も同じ精神でこの記事に取り組み、Holub氏が提供してくれたこの豊かな題材に感謝しています。
コード例について
コードは、私が最も使い慣れている言語、すなわちC#、F#、そしてHaskellで示します。C#のコードは、JavaやTypeScriptなどの類似言語のプログラマにも読んでいただけるように書くつもりです。一方で、F#やHaskell自体の解説は行いませんが、F#やHaskellに関する記事をスキップしても、C#の記事から学びを得られるように構成します。読者の大多数がHaskellを知っているとは想定していませんが、ある設計が関数型であるかどうかを評価する上で、Haskellは不可欠なツールだと考えています。また、F#はC#とHaskellの間を繋ぐ、教育的に優れた橋渡し役となります。
コードは、10米ドル(またはそれ以上)のサポート寄付をいただいた方に、リクエストに応じて提供しています。もしあなたが私の活動を定期的に支援してくださっている方であれば、感謝とともに、追加の寄付なしでコードをお渡しします。また、Oleksii Holub氏は自身のブログで、侵略者に対するウクライナへの支援を呼びかけています。この記事の公開日以降に、そこにリストされている慈善団体のいずれかに10米ドル以上を寄付したことを証明できる方にも、喜んでコードをお送りします。どちらの場合も、私宛にご連絡ください。
様々な代替案を示すにあたり、Gitのブランチを利用しています。各記事では、その記事に対応するブランチ名を記載するように努めます。
連載記事一覧
この連載では、複数のプログラミング言語を用いて、複数の代替案を提示します。記事の構成としては、まず設計アプローチごとに分類し、その中で言語別に分けるのが最も自然だと考えています。このリストは関数型プログラミングの設計カタログとしてご覧いただけますが、これが全てのアプローチを網羅しているわけではないことは承知しています。
- 曲レコメンデーション問題の仕様化
- 曲レコメンデーションのF#への移植
- 曲レコメンデーションのHaskellへの移植
- Impureim Sandwichパターンによる曲レコメンデーション
- C#によるImpureim Sandwich実装
- 実装におけるメモリ使用量の概念実証測定
- F#によるImpureim Sandwich実装
- HaskellによるImpureim Sandwich実装
- C#によるImpureim Sandwich実装
- コンビネータを用いた曲レコメンデーション
- C#コンビネータによる実装
- F#コンビネータによる実装
- Haskellコンビネータによる実装
- パイプ&フィルターパターンによる曲レコメンデーション
- .NET用Reactive Extensionsによる実装
- F#エージェントによる実装
- フリーモナドを用いた曲レコメンデーション
- Haskellフリーモナドによる実装
- F#フリーモナドによる実装
- C#フリーモナドによる実装
設計の代替案の中には、他の興味深いトピックへの寄り道を必要とするものもあります。F#やHaskellのコンテンツを読まなくても理解が進むように書くつもりですが、このブログの記事は、単独で完結しているものはほとんどありません。必要に応じてリンク先を参照していただくことを想定していますが、もし説明が不十分な点があれば、お気軽にコメントをお寄せください。
結論
この連載記事では、Oleksii Holub氏が提示した曲のレコメンデーション問題に対する、複数の代替的な設計アプローチを検討します。元のコード例は、副作用のある処理が混在しており(interleaved impurities)、フィルターや射影(projection)を多用しているため一見「関数型」に見えますが、厳密には関数型ではありません。
この元の例によって、問題の規模やその他の「現実世界」の制約のために、関数型プログラミングでは解決不可能な問題が存在する、という印象を一部の読者が持ってしまうかもしれません。本稿で提示する設計代替案のカタログは、読者の皆さんのそうした誤解を解くための私の試みです。
次回の記事: 曲レコメンデーション問題の仕様化