Преимущества использования семейств типов synonym очевидны — это функции уровня типа.
Но это не относится к семействам data, поэтому мой вопрос: каковы варианты использования для семейств данных? Где мне его использовать?
Преимущества использования семейств типов synonym очевидны — это функции уровня типа.
Но это не относится к семействам data, поэтому мой вопрос: каковы варианты использования для семейств данных? Где мне его использовать?
Одним из преимуществ является то, что семейства данных являются инъективными, в отличие от семейств типов.
Если у вас есть
type family TF a
data family DF a
Тогда вы знаете, что DF a ~ DF b
подразумевает, что a ~ b
, в то время как с TF вы этого не делаете -- для любого a
вы можете быть уверены, что DF a
является совершенно новым типом (точно так же, как [a]
- это тип, отличный от [b]
, если, конечно, a ~ b
), в то время как семейство типов может сопоставлять несколько типов ввода с одним и тем же существующим типом.
Во-вторых, семейства данных могут применяться частично, как и любой другой конструктор типов, а семейства типов — нет.
Это не особенно реальный пример, но, например, вы можете сделать:
data instance DF Int = DInt Int
data instance DF String = DString String
class C t where
foo :: t Int -> t String
instance C DF where -- notice we are using DF without an argument
-- notice also that you can write instances for data families at all,
-- unlike type families
foo (DInt i) = DString (show i)
По сути, DF
и DF a
сами по себе являются реальными, первоклассными, законными типами, как и любой другой тип, который вы объявляете с помощью data
. TF a
— это просто промежуточная форма, которая оценивается как тип.
Но я полагаю, что все это не очень поучительно, или, по крайней мере, не было для меня, когда я интересовался семействами данных и читал подобные вещи.
Вот эмпирическое правило, которого я придерживаюсь. Всякий раз, когда вы обнаружите, что повторяете шаблон, в котором у вас есть семейство типов, и для каждого входного типа вы объявляете новый тип data
для сопоставления семейства типов, лучше исключить посредника и вместо этого использовать семейство данных.
Реальный пример из библиотеки vector. vector
имеет несколько различных типов векторов: упакованные векторы, неупакованные векторы, примитивные векторы, сохраняемые векторы. Для каждого типа Vector
существует соответствующий изменяемый тип MVector
(обычные векторы неизменяемы). Так это выглядит так:
type family Mutable v :: * -> * -> * -- the result type has two type parameters
module Data.Vector{.Mutable} where
data Vector a = ...
data MVector s a = ...
type instance Mutable Vector = MVector
module Data.Vector.Storable{.Mutable} where
data Vector a = ...
data MVector s a = ...
type instance Mutable Vector = MVector
[etc.]
Теперь вместо этого я бы предпочел:
data family Mutable v :: * -> * -> *
module Data.Vector{.Mutable} where
data Vector a = ...
data instance Mutable Vector s a = ...
type MVector = Mutable Vector
module Data.Vector.Storable{.Mutable} where
data Vector a = ...
data instance Mutable Vector s a = ...
type MVector = Mutable Vector
[etc.]
Который кодирует инвариант, согласно которому для каждого типа Vector
существует ровно один тип Mutable Vector
и что между ними существует однозначное соответствие. Изменяемая версия Vector
всегда называется Mutable Vector
: это ее имя, и другого у нее нет. Если у вас есть Mutable Vector
, вы можете получить тип соответствующего неизменяемого Vector
, потому что он прямо здесь как аргумент типа. С type family Mutable
, как только вы применяете его к аргументу, он возвращает неуказанный тип результата (предположительно называемый MVector
, но вы не можете этого знать), и у вас нет возможности отобразить в обратном направлении.