正しい解決策は個別の型をリストに格納することであると想定することで、OOP の考え方を Haskell にもたらしています。その仮定を調べることから始めます。
通常、異なる型は同種のリストに格納します。これらは共通のインターフェイスをサポートするためです。共通のインターフェイスを除外して、それをリストに保存しないのはなぜですか?
残念ながら、あなたの質問はその共通インターフェースが何であるかを説明していないので、デモンストレーションとしていくつかの一般的な例を紹介します.
最初の例は、署名を持つ関数をすべてサポートする一連の値 、x
、y
およびです。z
Show
(Show a) => a -> String
後で表示したい型を保存する代わりにshow
、値を直接呼び出して、結果の文字列をリストに保存することができます。
list = [show x, show y, show z] :: String
show
Haskell は遅延言語であり、show
実際に文字列が必要になるまで実際には s を評価しないため、時期尚早に呼び出してもペナルティはありません。
または、型が次のような複数のメソッドをサポートしている可能性があります。
class Contrived m where
f1 :: m -> String -> Int
f2 :: m -> Double
上記の形式のクラスを、メソッドを値に部分的に適用した結果を含む同等の辞書に変換できます。
data ContrivedDict = ContrivedDict {
f1' :: String -> Int,
f2' :: Double }
...そして、このディクショナリを使用して、サポートすることが期待される共通インターフェイスに任意の値をパッケージ化できます。
buildDict :: (Contrived m) => m -> ContrivedDict
buildDict m = ContrivedDict { f1' = f1 m, f2' = f2 m }
次に、この共通インターフェース自体をリストに保存できます。
list :: [buildDict x, buildDict y, buildDict z]
ここでも、明確に型指定された値を格納する代わりに、それらの共通要素を取り出してリストに格納しました。
ただし、このトリックは常に機能するとは限りません。異常な例は、次の型を持つクラスの(+)
演算子など、同じ型の 2 つのオペランドを期待する任意の二項演算子です。Num
(Num a) => a -> a -> a
私の知る限り、二項演算を部分的に適用し、それが同じ型の 2 番目のオペランドに適用されることを保証するような方法で格納するための、辞書ベースの優れたソリューションはありません。このシナリオでは、存在型クラスがおそらく唯一の有効なアプローチです。ただし、型クラス ベースのアプローチよりも強力なトリックと変換が可能になるため、可能な限り辞書ベースのアプローチを使用することをお勧めします。
この手法の詳細については、Luke Palmer の記事Haskell Antipattern: Existential Typeclassを読むことをお勧めします。