私は実際に実用的な方法で完成するこの種のことをたくさんやったわけではないので、他の人がもっとできるようになることを願っています. ただし、Scala で Android アプリを作成しましたが、これにはいくつかの要件がありました。UI はインタラクティブで、「状態」はすべて SQLite データベースに保存されます。データベースと UI の両方が Android フレームワークとのインターフェースを必要としますが、Android フレームワークは Java 指向であり、Scala や関数型プログラミングには簡単には適合しません。
私がしたことは、モデル部分が純粋な操作のみをサポートする ADT のセットとして実装される MVC 設計のようなものを採用することでした。これには、モデル コードが Android フレームワークから完全に独立しているという追加の利点があったため、エミュレーターの外で好きな方法でテストできました。
これにより、コントローラー (ビューは非常に薄いレイヤーであり、ほとんどが Android の動作方法で構成されていました) に加えて、「データベースからモデルをロードする」および「モデルをデータベースに保存する」という追加の操作が残りました。Scala であるため、純粋なモデル コードを呼び出して実際のデータ操作を行う非純粋なコードを使用してこれらのパーツを実装しただけです。Haskell では、これらの部分はおそらく完全に IO モナドにあったでしょう[1]。
全体として、これにより、外部システムとのインターフェース時に「穀物に逆らう」必要なしに、問題のドメインを純粋に機能的な観点から考えることができました。データベース レイヤーは、データベース スキーマとデータ モデルに使用した ADT との間のマッピングの問題になります。これらの操作が失敗する可能性があるという事実への対処は、(DB 操作を開始した) コントローラーの責任であり、モデルには影響しません。コントローラの操作は、「このボタンが押されたとき、現在の状態を現在の状態で関数を呼び出した結果に設定し、表示テーブルを更新する」という概念的に非常に単純になります。最終的には、実際のモデル コードよりも、この不純な「接着剤」コードの方がはるかに多くなりましたが、それでも、この方法でプログラムを実行したことは勝利だったと思います。私のアプリの核心は複雑な構造化データの操作であり、それを正しく行うことは最も難しいことでした。残りは、設計が難しいというよりも、書くのが面倒でした。
これは、プログラムにかなりの量の計算がある場合に機能します (必ずしも大量のデータではなく、実際に計算するだけです)。プログラムがさまざまな外部システムをほぼ完全にくっつけている場合、必ずしも得られるものは多くありません。
[1] IO モナドは、コンソールから読み書きするためだけのものではないことに注意してください。プログラムの外部にある何かの状態によって結果が影響を受けるモデリング操作は、まさに IO モナドの目的です。通常、Haskeller は、常に外部システムと対話していない場合 (またはできれば対話している場合でも)、プログラムのほぼ全体で IO モナドの使用を避けようとします。「外部」からのイベントに応答してデータの純粋な計算を実行したり、複雑な場合は、外部イベントに応答して実行する必要がある IO アクションの純粋な計算を実行したりすることもできます。