存在型は、関数型プログラミングでは実際には悪い習慣とは見なされていません。あなたをつまずかせているのは、実存性の最も一般的に引用されている用途の1つが実存型クラスアンチパターンであり、多くの人が悪い習慣だと信じていることです。
このパターンは、多くの場合、すべてが同じタイプクラスを実装する異種タイプの要素のリストをどのように作成するかという質問への回答として作成されます。たとえば、Show
インスタンスを持つ値のリストが必要な場合があります。
{-# LANGUAGE ExistentialTypes #-}
class Shape s where
area :: s -> Double
newtype Circle = Circle { radius :: Double }
instance Shape Circle where
area (Circle r) = pi * r^2
newtype Square = Square { side :: Double }
area (Square s) = s^2
data AnyShape = forall x. Shape x => AnyShape x
instance Shape AnyShape where
area (AnyShape x) = area x
example :: [AnyShape]
example = [AnyShape (Circle 1.0), AnyShape (Square 1.0)]
このようなコードの問題は次のとおりです。
- で実行できる唯一の便利な操作
AnyShape
は、その領域を取得することです。
AnyShape
コンストラクターを使用して、いずれかの形状タイプをそのタイプに取り込む必要がありますAnyShape
。
結局のところ、このコードでは、この短いコードでは得られないものは実際には得られません。
class Shape s where
area :: s -> Double
newtype Circle = Circle { radius :: Double }
instance Shape Circle where
area (Circle r) = pi * r^2
newtype Square = Square { side :: Double }
area (Square s) = s^2
example :: [Double]
example = [area (Circle 1.0), area (Square 1.0)]
マルチメソッドクラスの場合には、同様の効果は、一般にコードする、代わりに「メソッドのレコード」を使用してのような型クラスを使用することによって、より簡単に達成することができShape
、あなたは、そのフィールドの「方法」であるレコードタイプ定義Shape
タイプ、円と正方形をShape
s に変換する関数を作成します。
しかし、それは存在型が問題であることを意味しません!たとえば、Rustには、トレイトオブジェクトと呼ばれる機能があります。これは、トレイト(Rustのタイプクラスのバージョン)上の実在型としてよく説明されます。存在する型クラスがHaskellのアンチパターンである場合、それはRustが悪い解決策を選んだことを意味しますか?番号!Haskellの世界での動機は、実際には原則ではなく、構文と利便性にあります。
より数学的な方法はAnyShape
、上からの型Double
が同型であることを指摘することです。それらの間には「ロスレス変換」があります(浮動小数点の精度を保つため)。
forward :: AnyShape -> Double
forward = area
backward :: Double -> AnyShape
backward x = AnyShape (Square (sqrt x))
厳密に言えば、どちらかを選択することでパワーを獲得したり失ったりすることはありません。つまり、選択は使いやすさやパフォーマンスなどの他の要因に基づいている必要があります。
また、実在型には、この異種リストの例以外にも他の用途があるので、それらを使用することをお勧めします。たとえば、HaskellのST
型は、外部では純粋であるが内部ではメモリ変換操作を使用する関数を作成できるため、コンパイル時の安全性を保証するために、存在型に基づいた手法を使用します。
したがって、一般的な答えは、一般的な答えはないということです。存在タイプの使用はコンテキストでのみ判断できます。また、異なる言語によって提供される機能と構文に応じて回答が異なる場合があります。