-
AllowAmbiguousTypes -
- Since:
-
7.8.1
Разрешить типы, которые, по-видимому, приведут к неприменимому связыванию.
Каждая пользовательская сигнатура типа подвергается проверке на неоднозначность. Проверка на неоднозначность отклоняет функции, которые никогда не могут быть вызваны. Например:
f :: C a => Int
Идея заключается в том, что не может быть законных вызовов f, потому что каждый вызов породит неоднозначное ограничение. Действительно, единственная цель проверки на неоднозначность — сообщить о функциях, которые, возможно, не могут быть вызваны. Мы могли бы безопасно исключить проверку на неоднозначность из сигнатур типов, за счет отсрочки ошибок неоднозначности до мест вызова. Действительно, расширение языка AllowAmbiguousTypes отключает проверку на неоднозначность.
Неоднозначность может быть тонкой. Рассмотрим этот пример, использующий функциональные зависимости:
class D a b | a -> b where .. h :: D Int b => Int
Int, возможно, и исправит b в месте вызова, поэтому сигнатура не должна отклоняться. Кроме того, зависимости могут быть скрыты. Рассмотрим
class X a b where ... class D a b | a -> b where ... instance D a b => X [a] b where... h :: X a b => a -> a
Здесь тип h выглядит неоднозначным в b, но вот допустимый вызов:
...(h [True])...
Это порождает ограничение (X [Bool] beta), и использование экземпляра означает, что нам нужен (D Bool beta), что исправляет beta через fundep D.
За всеми этими особыми случаями стоит простой руководящий принцип. Рассмотрим
f :: type f = ...blah... g :: type g = f
Можно подумать, что определение g обязательно пройдет проверку типов! В конце концов, у f ровно тот же тип, и g=f. Но на самом деле тип f экземпляризован, а экземпляризованные ограничения решаются по отношению к ограничениям, связанным с сигнатурой g. Таким образом, в случае неоднозначного типа решение завершится неудачей. Например, рассмотрим ранее определённое f :: C a => Int:
f :: C a => Int f = ...blah... g :: C a => Int g = f
В определении g мы экземпляризируем до (C alpha) и попытаемся вывести (C alpha) из (C a), но потерпим неудачу.
Итак, на самом деле мы используем это как наше определение неоднозначности: тип ty является неоднозначным тогда и только тогда, когда ((undefined :: ty) :: ty) не пройдёт проверку типов. Мы используем очень похожую проверку для выведенных типов, чтобы убедиться, что они тоже недвусмысленны.
Отключение проверки на неоднозначность. Даже если функция имеет неоднозначный тип согласно «руководящему принципу», возможно, эта функция вызываема. Например:
class D a b where ... instance D Bool b where ... strange :: D a b => a -> a strange = ...blah... foo = strange True
Здесь тип strange является неоднозначным, но вызов в foo правильный, так как он порождает ограничение (D Bool beta), которое разрешается экземпляром (D Bool b).
Другой способ устранения неоднозначности в месте вызова — использование расширения TypeApplications для указания типов. Например:
class D a b where h :: b instance D Int Int where ... main = print (h @Int @Int)
Здесь a является неоднозначным в определении D, но позже задаётся как Int с помощью применений типов.
AllowAmbiguousTypes позволяет полностью отключить проверку на неоднозначность.
Иногда AllowAmbiguousTypes не сочетается с RankNTypes. Например:
foo :: forall r. (forall i. (KnownNat i) => r) -> r foo f = f @1 boo :: forall j. (KnownNat j) => Int boo = .... h :: Int h = foo boo
Эта программа будет отклонена как неоднозначная, так как GHC не будет унифицировать переменные типов j и i.
В отличие от предыдущих примеров, в настоящее время невозможно вручную разрешить неоднозначность с помощью TypeApplications.
Примечание
Историческая справка. Раньше GHC накладывал более жёсткие и менее основанные на принципах условия на сигнатуры типов. Для типа forall tv1..tvn (c1, ...,cn) => type GHC ранее требовал
- чтобы каждая универсальная переменная типа
tviбыла «доступна» изtype, и - что каждое ограничение
ciупоминало по крайней мере одну из универсальных переменных типаtvi. Эти произвольные ограничения полностью подразумеваются новой проверкой на неоднозначность.