Добросовестная неоднозначность при использовании PolyKinds и семейств типов
У меня есть два семейства типов, одно из которых сопоставляет один тип другому типу другого типа и полиморфной функции:
{-# LANGUAGE PolyKinds, TypeFamilies, FlexibleContexts, ScopedTypeVariables #-}
type family F (a :: k1) :: k2
type family G (a :: k2) :: *
f :: forall k1 k2 (a :: k1) (p :: k1 -> *) . p (a :: k1) -> G (F (a :: k1) :: k2)
f = undefined
Этот код не проверяет следующее сообщение об ошибке:
• Couldn't match type ‘G k20 (F k20 k1 a)’ with ‘G k2 (F k2 k1 a)’
Expected type: p a -> G k2 (F k2 k1 a)
Actual type: p a -> G k20 (F k20 k1 a)
NB: ‘G’ is a non-injective type family
но я не могу понять, откуда возникла двусмысленность и как я могу указать недостающие виды?
Когда я использую только одно семейство типов, это работает:
g :: forall k1 k2 (a :: k1) (p :: k1 -> *) (q :: k2 -> *). p (a :: k1) -> q (F (a :: k1) :: k2)
g = undefined
2 ответа
f :: forall k1 k2 (a :: k1) (p :: k1 -> *). p a -> G (F a :: k2)
Позвольте мне попытаться сказать:
x :: [String]
x = f (Just 'a')
Это идет и создает экземпляры f
с k1 ~ Type
, a ~ Char
, а также p ~ Maybe
f :: forall k2. Maybe Char -> G (F Char :: k2)
Что теперь? Ну мне дальше нужно G (F Char :: k2) ~ [String]
, но G
это неинъективное семейство типов, поэтому нельзя сказать, какой из его аргументовk2
а также F Char :: k2
-должно быть. Следовательно, определение x
в ошибке; k2
является неоднозначным, и невозможно сделать заключение для него.
Тем не менее, вы можете довольно четко увидеть, что не использовать f
когда-нибудь сможет сделать вывод k2
, Причина в том, что k2
появляется только в типе f
под неинъективным заявлением семейного типа (другая "плохая позиция" - это LHS =>
). Он никогда не появляется в положении, в котором он может быть выведен. Поэтому без расширения вроде TypeApplications
, f
бесполезен, и никогда не может быть упомянут без появления этой ошибки. GHC дает обнаруживает это и вызывает ошибку при определении, а не использования. Появляющееся сообщение об ошибке примерно такое же, что и при попытке:
f0 :: forall k10 k20 (a :: k10) (p0 :: k10 -> *). p0 a0 -> G (F a0 :: k20)
f0 = f
Это приводит к тому же несоответствию типов, так как k20
из f0
должен соответствовать k2
из f1
,
Вы можете заставить замолчать ошибку в определении f
путем включения AllowAmbiguousTypes
, который отключает эту проверку бесполезности во всех определениях. Однако, в одиночку, это просто выдвигает ошибку при каждом использовании f
, Для того, чтобы на самом деле позвонить f
, вы должны включить TypeApplications
:
f0 :: forall k10 k20 (a :: k10) (p0 :: k10 -> *). p0 a0 -> G (F a0 :: k20)
f0 = f @k10 @k20 @a0 @p0
Альтернатива TypeApplications
это что-то вроде Data.Proxy.Proxy
, но это в значительной степени устарело, за исключением случаев более высокого ранга. (И даже тогда, это будет действительно без работы, когда у нас будет что-то вроде type-lambdas.)
Изначально проверка неоднозначности была предназначена для отклонения функций, которые никогда не могут быть вызваны из-за параметров типа и ограничений, которые не могут быть выведены из явных аргументов функции.
Однако в GHC 8.6.x таких функций нет, потому что все можно сделать явным TypeApplications
, Я рекомендую просто включить AllowAmbiguousTypes
а также TypeApplications
, Предупреждение GHC о неоднозначных типах само по себе не очень информативно, поскольку оно отклоняет многие из допустимых вариантов использования приложений типов.