На расстоянии семейства данных немного похожи на GADT, так как два конструктора для семейства данных могут давать результаты разных типов. Но семейства данных не совпадают с GADT! Они действительно намного больше похожи на типовые семьи. Вы не можете на самом деле соответствовать Add
или Sub
, пока не узнаете, что у вас есть Operator 'Numeric
. Почему это? Вы можете думать об этом оперативно. Каждый конструктор должен иметь «тег», чтобы выражения case
могли их различать. Если два экземпляра Data определены в разных модулях, они могут в конечном итоге использовать один и тот же тег для разных конструкторов! Более того, экземпляр newtype даже не получает тега, поэтому нет никакого способа их различить! Как указывает Чи, вы можете обойти это, обернув единичный элемент в своей экзистенции, чтобы отслеживать, какой экземпляр данных у вас есть.
Насколько я понимаю, семейства данных на самом деле не предлагают много, еслилюбая, сила, которую невозможно получить без них. Давайте посмотрим, как семейство данных, несколько более сложное, чем ваше, может быть очень неловко выражено с помощью нового типа, семейства типов и синонимов шаблонов.
import Data.Kind (Type)
data Typ = Numeric Bool | Boolean
newtype Operator t = Operator (OperatorF t)
type family OperatorF (t :: Typ) :: Type
type instance OperatorF ('Numeric b) = OpNum b
type instance OperatorF 'Boolean = OpBool
-- This makes no sense; it's just for demonstration
-- purposes.
data OpNum b where
Add' :: OpNum 'True
Sub' :: OpNum 'False
data OpBool = And' | Or'
pattern Add :: () => (b ~ 'True) => Operator ('Numeric b)
pattern Add = Operator Add'
pattern Sub :: () => (b ~ 'False) => Operator ('Numeric b)
pattern Sub = Operator Sub'
pattern And :: Operator 'Boolean
pattern And = Operator And'
pattern Or :: Operator 'Boolean
pattern Or = Operator Or'