この「if」と「no if」の全体から、 Expression Problem 1が思い浮かびます。基本的に、if ステートメントを使用するプログラミングと、if ステートメントを使用しないプログラミングはカプセル化と拡張性の問題であり、if ステートメント2を使用する方が良い場合もあれば、メソッド/関数ポインターを使用した動的ディスパッチを使用する方が良い場合もあります。
何かをモデル化する場合、2 つの軸について考慮する必要があります。
- 対処する必要がある入力のさまざまなケース (またはタイプ)。
- これらの入力に対して実行したいさまざまな操作。
この種のものを実装する 1 つの方法は、if ステートメント / パターン マッチング / ビジター パターンを使用することです。
data List = Nil | Cons Int List
length xs = case xs of
Nil -> 0
Cons a as -> 1 + length x
concat xs ys = case ii of
Nil -> jj
Cons a as -> Cons a (concat as ys)
もう 1 つの方法は、オブジェクト指向を使用することです。
data List = {
length :: Int
concat :: (List -> List)
}
nil = List {
length = 0,
concat = (\ys -> ys)
}
cons x xs = List {
length = 1 + length xs,
concat = (\ys -> cons x (concat xs ys))
}
最初のバージョンで if ステートメントを使用すると、データ型に新しい操作を簡単に追加できることがわかります。新しい関数を作成し、その中でケース分析を行うだけです。一方で、これにより、データ型に新しいケースを追加することが難しくなります。これは、プログラムに戻ってすべての分岐ステートメントを変更することを意味するためです。
2番目のバージョンは一種の反対です。新しいケースをデータ型に追加するのは非常に簡単です。新しい「クラス」を作成し、実装する必要がある各メソッドに対して何をすべきかを指示するだけです。ただし、インターフェイスに新しい操作を追加することは、インターフェイスを実装したすべての古いクラスに新しいメソッドを追加することを意味するため、現在は困難です。
Expression Problem を解決し、新しいケースと新しい操作の両方をモデルに簡単に追加できるようにするために、言語が使用するさまざまなアプローチがあります。ただし、これらのソリューションには長所と短所があります3。したがって、一般的には、拡張を容易にしたい軸に応じて、OO ステートメントと if ステートメントのどちらかを選択するのが良い経験則だと思います。
とにかく、あなたの質問に戻ると、指摘したいことがいくつかあります。
1 つ目は、すべての if ステートメントを取り除き、それらをメソッド ディスパッチに置き換えるという OO の「マントラ」は、ほとんどの OO 言語が型安全な代数データ型を持たないことに関係していると思います。 statesnts" はカプセル化に適していません。タイプ セーフにする唯一の方法はメソッド呼び出しを使用することなので、if ステートメントを使用するプログラムをVisitor パターン4を使用するプログラムに変換することをお勧めします。さらに悪いことに、visitor パターンを使用する必要があるプログラムを単純なメソッド ディスパッチを使用するプログラムに変換します。間違った方向に簡単に拡張できます。
2 番目のことは、私は、できるという理由だけで物事を関数に分割することはあまり好きではないということです。特に、すべての関数がわずか 5 行であり、他の関数を大量に呼び出すスタイルは非常に読みにくいと思います。
最後に、あなたの例は実際には if ステートメントを取り除いていないと思います。基本的に、あなたがしていることは、Integers から新しいデータ型への関数を持っていることです (Big 用と Small 用の 2 つのケースがあります)。その後、データ型を操作するときに if ステートメントを使用する必要があります。
data Size = Big | Small
toSize :: Int -> Size
toSize n = if n < 10 then Small else Big
someOp :: Size -> String
someOp Small = "Wow, its small"
someOp Big = "Wow, its big"
式の問題の観点に戻ると、 toSize / isSmall 関数を定義する利点は、数値がどのケースに収まるかを選択するロジックを 1 つの場所に配置し、関数がその後のケースでのみ動作できることです。ただし、これはコードから if ステートメントを削除したという意味ではありません。toSize がファクトリ関数であり、Big と Small がインターフェイスを共有するクラスである場合は、コードから if ステートメントが削除されます。ただし、 isSmall がブール値または列挙型を返すだけの場合は、以前と同じ数の if ステートメントが存在します。(そして、将来、新しいメソッドや新しいケースを簡単に追加できるようにするかどうかに応じて、使用する実装を選択する必要があります。Medium など)。
1 - 問題の名前は、「式」データ型 (数値、変数、部分式の加算/乗算など) があり、評価関数などを実装したいという問題に由来します。
2 - または、代数データ型に対するパターン マッチング、より型を安全にしたい場合...
3 - たとえば、「ディスパッチャー」がそれらを見ることができる「最上位」ですべてのマルチメソッドを定義する必要がある場合があります。他のコード内に深くネストされた if ステートメント (およびラムダ) を使用できるため、これは一般的なケースと比較した場合の制限です。
4 - 本質的に代数データ型の「チャーチ エンコーディング」