-
RankNTypes -
- Подразумевает:
- С:
-
6.8.1
- Статус:
Разрешает типы произвольного ранга.
-
Rank2Types -
- С:
-
6.8.1
- Статус:
-
Устаревшее
Устаревшее псевдоним для
RankNTypes.
Система типов GHC поддерживает полиморфизм произвольного ранга с явным универсальным квантификатором в типах. Например, следующие типы являются допустимыми:
f1 :: forall a b. a -> b -> a g1 :: forall a b. (Ord a, Eq b) => a -> b -> a f2 :: (forall a. a->a) -> Int -> Int g2 :: (forall a. Eq a => [a] -> a -> Bool) -> Int -> Int f3 :: ((forall a. a->a) -> Int) -> Bool -> Bool
Здесь f1 и g1 — типы ранга 1, которые можно записать в стандартном Haskell (например, f1 :: a->b->a). forall явно указывает на универсальное квантификацию, неявно добавляемое Haskell.
Функции f2 и g2 имеют типы ранга 2; forall стоит слева от стрелки функции. Как показывает g2, полиморфный тип слева от стрелки функции может быть перегружен.
Функция f3 имеет тип ранга 3; у нее типы ранга 2 слева от стрелки функции.
Параметр языка RankNTypes (который подразумевает ExplicitForAll) включает типы высшего ранга. То есть вы можете вкладывать forall произвольно глубоко в стрелки функций. Например, тип со `forall` (также называемый «схемой типа»), включая контекст класса типов, является допустимым:
- Слева или справа от стрелки функции.
- В качестве аргумента конструктора или типа поля в объявлении типа данных. Например, любой из
f1, f2, f3, g1, g2выше был бы допустимым типом поля. - В качестве типа неявного параметра.
- В сигнатуре шаблона типа (см. Лексически связанные переменные типов).
В частности, в объявлениях data и newtype аргументы конструктора могут быть полиморфными типами любого ранга; см. примеры в Примеры. Обратите внимание, что объявленные типы всё равно являются мономорфными. Это важно, потому что по умолчанию GHC не будет подставлять переменные типов в полиморфный тип (Импредикативный полиморфизм).
Обратите внимание, что параметр RankNTypes также требуется для любого типа с forall или контекстом справа от стрелки. Например:
h1 :: Int -> (forall a. a -> a) h1' :: forall a. Int -> (a -> a) k1 :: Int -> Ord a => a -> a k1' :: Ord a => Int -> a -> a
Функция h1 имеет тип ранга 1; она имеет такое же поведение, как h1', за исключением разного порядка аргументов. Это имеет значение, если явно указать тип с помощью видимого применения типа (используя TypeApplications): мы бы написали h1 3 @Bool True вместо h1' @Bool 3 True. Аналогично, k1 имеет тип ранга 1; он отличается от k1' только порядком аргументов. Поскольку типы h1 и k1 не допускаются в Haskell-98, мы также требуем, чтобы пользователи включили RankNTypes для их написания (что кажется более разумным, чем изобретение отдельного расширения только для этого случая).
Устаревший параметр языка Rank2Types — синоним для RankNTypes. Раньше они определяли более тонкие различия, которых GHC больше не делает.
6.4.19.1. Примеры
Вот примеры объявления data и newtype , конструкторы которых имеют полиморфные аргументы:
data T a = T1 (forall b. b -> b -> b) a
data MonadT m = MkMonad { return :: forall a. a -> m a,
bind :: forall a b. m a -> (a -> m b) -> m b
}
newtype Swizzle = MkSwizzle (forall a. Ord a => [a] -> [a])
Конструкторы имеют тип ранга 2:
T1 :: forall a. (forall b. b -> b -> b) -> a -> T a
MkMonad :: forall m. (forall a. a -> m a)
-> (forall a b. m a -> (a -> m b) -> m b)
-> MonadT m
MkSwizzle :: (forall a. Ord a => [a] -> [a]) -> Swizzle
В более ранних версиях GHC было возможно опустить forall в типе конструктора, если был явный контекст. Например:
newtype Swizzle' = MkSwizzle' (Ord a => [a] -> [a])
С GHC 8.0 объявления, такие как MkSwizzle', вызовут ошибку «вне области действия».
Вы создаете значения типов T1, MonadT, Swizzle, применяя конструктор к подходящим значениям, как обычно. Например,
a1 :: T Int
a1 = T1 (\x y->x) 3
a2, a3 :: Swizzle
a2 = MkSwizzle sort
a3 = MkSwizzle reverse
a4 :: MonadT Maybe
a4 = let r x = Just x
b m k = case m of
Just y -> k y
Nothing -> Nothing
in
MkMonad r b
mkTs :: (forall b. b -> b -> b) -> a -> a -> [T a]
mkTs f x y = [T1 f x, T1 f y]
Тип аргумента, как обычно, может быть более общим, чем требуемый тип, как показывает (MkSwizzle reverse). (reverse не требует ограничения Ord).
При использовании сопоставления с образцом связанные переменные теперь могут иметь полиморфные типы. Например:
f :: T a -> a -> (a, Char)
f (T1 w k) x = (w k x, w 'c' 'd')
g :: (Ord a, Ord b) => Swizzle -> [a] -> (a -> b) -> [b]
g (MkSwizzle s) xs f = s (map f (s xs))
h :: MonadT m -> [m a] -> m [a]
h m [] = return m []
h m (x:xs) = bind m x $ \y ->
bind m (h m xs) $ \ys ->
return m (y:ys)
В функции h мы используем селекторы записей return и bind для извлечения полиморфных функций связывания и возврата из структуры данных MonadT, а не с помощью сопоставления с образцом.
6.4.19.2. Подстановка
Предположим:
f1 :: (forall a b. Int -> a -> b -> b) -> Bool g1 :: forall x y. Int -> y -> x -> x f2 :: (forall a. (Eq a, Show a) => a -> a) -> Bool g2 :: forall x. (Show x, Eq x) => x -> x
тогда f1 g1 и f2 g2 оба правильно типизированы, несмотря на разный порядок переменных типа и ограничений. Что происходит, так это то, что аргумент подставляется, а затем обобщается, чтобы соответствовать типу, ожидаемому функцией.
Но эта подстановка и обобщение происходят только на верхнем уровне типа. В частности, ничего такого не происходит, если `forall` находятся под стрелкой. Например:
f3 :: (Int -> forall a b. a -> b -> b) -> Bool g3a :: Int -> forall x y. x -> y -> y g3b :: forall x. Int -> forall y. x -> y -> y g3c :: Int -> forall x y. y -> x -> x f4 :: (Int -> forall a. (Eq a, Show a) => a -> a) -> Bool g4 :: Int -> forall x. (Show x, Eq x) => x -> x) -> Bool
Тогда применение f3 g3a правильно типизировано, потому что g3a имеет тип, соответствующий типу, ожидаемому f3. Но f3 g3b не правильно типизировано, потому что `forall` находятся в разных местах. Также не правильно типизировано f3 g3c, где `forall` находятся в одном месте, но переменные в другом порядке. Аналогично, f4 g4 не правильно типизировано, потому что ограничения появляются в другом порядке.
Эти примеры можно сделать типизируемыми с помощью расширения eta. Например, f3 (\x -> g3b x) правильно типизирован, аналогично f3 (\x -> g3c x) и f4 (\x -> g4 x).
Похожее явление наблюдается для секций операторов. Например, (\`g3a\` "hello") не правильно типизирован, но его можно сделать типизируемым, расширив его с помощью eta до \x -> x \`g3a\` "hello".
-
DeepSubsumption -
- С:
-
9.2.4
Смягчает простые правила подстановки, неявно вставляя расширения eta при сопоставлении типов функций с различными структурами квантификации.
Расширение DeepSubsumption смягчает упомянутое требование, что `forall` должны появляться в одном месте. GHC вместо этого автоматически перепишет выражения, такие как f x типа ty1 -> ty2 в (\ (y :: ty1) -> f x y); это называется расширением eta. См. раздел 4.6 в Практическое выведение типов для типов произвольного ранга, где этот процесс называется «глубокой схолемизацией».
Обратите внимание, что эти расширения eta могут безмолвно изменить семантику программы пользователя:
h1 :: Int -> forall a. a -> a h1 = undefined h2 :: forall b. Int -> b -> b h2 = h1
С DeepSubsumption, GHC примет эти определения, вставив неявное расширение eta:
h2 = \ i -> h1 i
Это означает, что h2 `seq` () не вызовет ошибку, даже если h1 `seq` () вызовет.
Историческая справка: Глубокая схолемизация первоначально была удалена из языка предложением GHC Proposal #287, но была повторно введена в качестве части расширения DeepSubsumption вслед за GHC Proposal #511.