私は典型的なOOP開発者です。しかし、純粋関数型プログラミング言語を発見して以来、なぜOOP はほとんどのビジネス ケースを合理的な方法で解決しているように思えたからです。
ソフトウェア開発の経験から、私は今、より簡潔で表現力豊かな言語を求める段階に至りました。私は通常、C# でソフトウェアを作成しますが、最新のプロジェクトでは思い切って F# を使用してビジネス サービスを構築することにしました。そうすることで、純粋に機能的なアプローチで分離がどのように行われるかを理解するのが非常に難しくなっています。
ケースはこうです。データソースはWooCommerceですが、関数定義をそれに結び付けたくありません。それ特定のデータソース。C
#では、次のようなサービスが欲しいのは明らかです。
public record Category(string Name);
public interface ICategoryService
{
Task<IEnumerable<Category>> GetAllAsync();
}
// With a definition for the service that specifies WooCommerce
public class WcCategoryService : ICategoryService
{
private readonly WCRestEndpoint wcRest;
// WooCommerce specific dependencies
public WcCategoryService(WCRestEndpoint wcRest)
{
this.wcRest = wcRest;
}
public Task<IEnumerable<Category>> GetAllAsync()
{
// Call woocommerce REST and map the category to our domain category
}
}
今後、カテゴリを提供するための新しいストアが必要であると判断した場合、その特定のサービスに対して新しい実装を定義し、挿入されたタイプを置き換えて、この変更によって依存関係が混乱しないようにすることができます。
関数依存アプローチがどのように解決されるかを理解しようとして、私はこのケースに遭遇しました(「ドメインモデリングを関数型にする」を読んでいます)。型シグネチャが依存関係を直接定義するため、上記のC#の同等物は高度に結合された定義になります。
type Category = { Name: string }
type GetCategories =
WCRestEndpoint
-> Category list
突然、カテゴリのソースを変更する場合、機能シグネチャを変更するか、使用する新しい定義を提供する必要がありますが、これはアプリケーション全体に波及するため、あまり堅牢ではありません。
私が知りたいのは、何か根本的なことを誤解しているのではないかということです。
私のOOP脳では、次のようなことしか思いつきません
type Category = { Name: string }
// No longer directly dependent on WCRestEndpoint
type GetCategories = unit -> Category list
// But the definition would require scoped inclusion of the dependency
// Also how does the configuration get passed in without having the core library be dependent on the Environment or a config in the assembly?
let rest = WCRestEndpoint(/* Config... */)
type getCategories: GetCategories =
fun () ->
let wcCategories = rest.GetCategories()
// Convert the result into a Category type
調べてみたところ、純粋に機能的なアプローチで変更がどのように処理されるかについての説明は見つからず、根本的な何か誤解しているのではないかと考えるようになりました。
関数型シグネチャを実装固有の型に結び付けずに、関数 API を公開するにはどうすればよいでしょうか? これについて私が間違って考えているのでしょうか?
ベストアンサー1
私は何年もこの疑問に悩んだが、間違った見方をしていたことに気づいた。オブジェクト指向開発と依存性注入の経験から、依存性注入に代わる機能的な方法を探し続けた。そしてついに、依存性注入はすべてを不純にするつまり、そのアプローチを使用することはできません(部分的な適用さえも)を申請したい場合は機能的な建築。
依存関係に焦点を当てることは間違いです。代わりに、純粋な関数を書くことに焦点を当ててください。依存性逆転の原則行動ややりとりに焦点を当てるのではなく、データ関数が何らかのデータを必要とする場合は、それを引数として渡します。関数が決定を下す必要がある場合は、データ構造として返す。
値のリストを使用する例を示していませんCategory
が、そのようなデータに依存する関数の型は次のようになります。
Category list -> 'a
このような機能は、ソースカテゴリの。Category
ドメイン モデルの一部であるタイプ自体にのみ依存します。
最終的にはどこかからカテゴリを取得する必要がありますが、この作業ではシステムの境界まで押し広げます。例Main
:
let Main () =
let categories = getCategories ()
let result = myFunction categories
result
したがって、カテゴリの取得方法について考えが変わった場合は、コードを1行変更するだけで済みます。このようなアーキテクチャはサンドイッチに似ている、アプリケーションの純粋な中心を取り巻く不純な行為。また、機能コア、命令シェル。