Haskell Language
Семейство типов
Поиск…
Типовые синонимы
Семейство синонимов типов - это только функции на уровне шрифта: они связывают типы параметров с типами результатов. Они представлены в трех разных вариантах.
Закрытые семейства синонимов
Они работают так же, как обычные функции Haskell на уровне значений: вы указываете некоторые предложения, сопоставляя некоторые типы с другими:
{-# LANGUAGE TypeFamilies #-}
type family Vanquisher a where
Vanquisher Rock = Paper
Vanquisher Paper = Scissors
Vanquisher Scissors = Rock
data Rock=Rock; data Paper=Paper; data Scissors=Scissors
Открытые семейства синонимов типа
Они больше похожи на экземпляры typeclass: каждый может добавлять дополнительные предложения в другие модули.
type family DoubledSize w
type instance DoubledSize Word16 = Word32
type instance DoubledSize Word32 = Word64
-- Other instances might appear in other modules, but two instances cannot overlap
-- in a way that would produce different results.
Синонимы, связанные с классом
Семейство открытого типа также можно комбинировать с фактическим классом. Обычно это делается, когда, как и в случае связанных семейств данных , некоторый метод класса нуждается в дополнительных вспомогательных объектах, и эти вспомогательные объекты могут быть разными для разных экземпляров, но могут также совместно использоваться. Хорошим примером является класс VectorSpace :
class VectorSpace v where
type Scalar v :: *
(*^) :: Scalar v -> v -> v
instance VectorSpace Double where
type Scalar Double = Double
μ *^ n = μ * n
instance VectorSpace (Double,Double) where
type Scalar (Double,Double) = Double
μ *^ (n,m) = (μ*n, μ*m)
instance VectorSpace (Complex Double) where
type Scalar (Complex Double) = Complex Double
μ *^ n = μ*n
Обратите внимание, что в первых двух случаях реализация Scalar одинакова. Это было бы невозможно с ассоциированным семейством данных: семейства данных инъективны , семейства синонимов типа нет.
Хотя неинъективность открывает некоторые возможности, подобные описанным выше, это также затрудняет определение типа. Например, следующее правило не будет выглядеть:
class Foo a where
type Bar a :: *
bar :: a -> Bar a
instance Foo Int where
type Bar Int = String
bar = show
instance Foo Double where
type Bar Double = Bool
bar = (>0)
main = putStrLn (bar 1)
В этом случае компилятор не может знать, какой экземпляр использовать, потому что аргумент bar является собственно полиморфным литералом Num . И Bar функции типа не может быть разрешен в «обратном направлении», именно потому, что он не является инъективным † и, следовательно, не обратим (может быть более одного типа с Bar a = String ).
† Только с этими двумя экземплярами он фактически является инъективным, но компилятор не может знать, что кто-то не добавит больше экземпляров позже и тем самым нарушит поведение.
Типы данных
Семейства данных могут использоваться для создания типов данных, которые имеют разные реализации на основе их аргументов типа.
Автономные семейства данных
{-# LANGUAGE TypeFamilies #-}
data family List a
data instance List Char = Nil | Cons Char (List Char)
data instance List () = UnitList Int
В приведенной выше декларации Nil :: List Char и UnitList :: Int -> List ()
Связанные семейства данных
Семейства данных также могут быть связаны с классами. Это часто полезно для типов с «вспомогательными объектами», которые требуются для универсальных методов класса, но должны содержать различную информацию в зависимости от конкретного экземпляра. Например, для индексирования местоположений в списке требуется только одно число, тогда как в дереве вам нужно число, указывающее путь на каждом узле:
class Container f where
data Location f
get :: Location f -> f a -> Maybe a
instance Container [] where
data Location [] = ListLoc Int
get (ListLoc i) xs
| i < length xs = Just $ xs!!i
| otherwise = Nothing
instance Container Tree where
data Location Tree = ThisNode | NodePath Int (Location Tree)
get ThisNode (Node x _) = Just x
get (NodePath i path) (Node _ sfo) = get path =<< get i sfo
приемистости
Тип Семьи не обязательно инъективны. Поэтому мы не можем вывести параметр из приложения. Например, в servant , заданном Server a типа Server a мы не можем вывести тип a . Чтобы решить эту проблему, мы можем использовать Proxy . Например, в servant функция serve имеет тип ... Proxy a -> Server a -> ... Мы можем сделать вывод , из a Proxy a потому , что Proxy определяются data , которые инъективны.