16.1. Стандарты Haskell против Glasgow Haskell: несоответствие языка
В этом разделе перечислены неточности Glasgow Haskell в его реализации Haskell 98 и Haskell 2010. Также см. раздел «Когда возникают проблемы» (Что делать, когда что-то идет не так) для информации о сбоях, утечках памяти и других нежелательных явлениях.
Ограничения здесь перечислены в порядке Haskell Report (приблизительно).
16.1.1. Отклонение от Haskell 98 и Haskell 2010
GHC стремится вести себя (в основном) как компилятор Haskell 98 или Haskell 2010, если вы скажете ему пытаться так себя вести с флагами Haskell98 и Haskell2010. Известные отклонения от стандартов описаны ниже. Если не указано иное, отклонение относится к режиму Haskell 98 и Haskell 2010.
16.1.1.1. Лексическая синтаксис
- Некоторые лексические правила относительно квалифицированных идентификаторов немного отличаются в GHC по сравнению с отчётом Haskell. Когда у вас есть ⟨module⟩
.⟨reservedop⟩, напримерM.\, GHC будет интерпретировать его как единый квалифицированный оператор вместо двух лексемMи.\. -
forallвсегда является зарезервированным ключевым словом. Это противоречит отчёту Haskell, который допускает использование переменных и типов переменных с именемforall. Обратите внимание, что это не означает, что GHC всегда включает расширениеExplicitForAll. Даже без включённого расширения зарезервированиеforallв качестве ключевого слова имеет значение. Например, GHC не будет парсить подпись типаfoo :: forall x. - Оператор
(!), когда он написан в префиксной форме (предваряемый пробелом и не последуемый пробелом, как вf !x = ...), интерпретируется как шаблон bang, в отличие от отчёта Haskell, который предписывает рассматривать!как оператор независимо от окружающих пробелов. Обратите внимание, что это не означает, что GHC всегда включаетBangPatterns. Без расширения GHC выдаст ошибку разбораf !x, попросив включить расширение. -
Неразрушаемые шаблоны должны быть написаны в префиксной форме:
f ~a ~b = ... -- accepted by both GHC and the Haskell Report f ~ a ~ b = ... -- accepted by the Haskell Report but not GHC
Когда они написаны в не-префиксной форме,
(~)GHC рассматривает как обычный инфиксный оператор.См. Предложение GHC №229 для точных правил.
-
Комментарии строгости в объявлениях данных должны быть написаны в префиксной форме:
data T = MkT !Int -- accepted by both GHC and the Haskell Report data T = MkT ! Int -- accepted by the Haskell Report but not GHC
См. Предложение GHC №229 для точных правил.
-
Шаблоны «как» не должны быть окружены пробелами с обеих сторон:
f p@(x, y, z) = ... -- accepted by both GHC and the Haskell Report -- accepted by the Haskell Report but not GHC: f p @ (x, y, z) = ... f p @(x, y, z) = ... f p@ (x, y, z) = ...
Когда они окружены пробелами с обеих сторон,
(@)GHC рассматривает как обычный инфиксный оператор.Когда предваряются, но не последуют пробелами,
(@)рассматривается как видимое применение типа.См. Предложение GHC №229 для точных правил.
-
Отчёт Haskell допускает любые десятичные числа Unicode в десятичных литералах. Однако GHC принимает только ASCII-цифры:
ascDigit → 0 | 1 | … | 9 decimal → ascDigit {ascDigit} -
GHC более лоялен в выборе допустимых символов в идентификаторах. Другие символы Unicode рассматриваются как строчные буквы, поэтому идентификаторы переменных могут начинаться с них. Класс цифр включает все числа Unicode вместо только десятичных. Модификаторы и знаки без пробелов могут появляться в конце идентификаторов:
uniSmall → any Unicode Lowercase Letter or Other Letter uniDigit → any Unicode Decimal Number, Letter Number or Other Number uniIdchar → any Unicode Modifier Letter or Non-Spacing Mark idchar → small | large | digit | uniIdchar | ' varid → small {idchar} ⟨reservedid⟩ conid → large {idchar} - GHC допускает избыточные скобки вокруг имени функции в части
funlhsобъявлений. То есть GHC успешно распознает объявление вроде((f)) x = <rhs>для любого количества скобок вокругf.
16.1.1.2. Контекстно-свободный синтаксис
-
В режиме Haskell 98 (но не в режиме Haskell 2010) GHC немного менее строг к правилу выравнивания при использовании в
doвыражениях. В частности, ограничение, что «вложенный контекст должен быть отступающим дальше вправо, чем окружающий контекст», ослабляется, чтобы позволить вложенному контексту быть на одном уровне с окружающим контекстом, если вложенный контекст является выражениемdo.Например, следующий код, в котором контекст
doвложен в контекст case, а операторfeed animalотступает на такое же значение, как и альтернатива case, принимается GHC:main = case animal of Wombat -> do feed animalНо этот код с обратным вложением не принимается:
main = do case animal of Wombat -> feed animal
Это поведение контролируется расширением
NondecreasingIndentation.
-
NondecreasingIndentation -
- Since:
-
7.2.1
- Status:
-
Включено в
Haskell98
Разрешить вложенным контекстам иметь тот же уровень отступа, что и окружающий контекст.
-
GHC не выполняет разрешение фиктивности в выражениях во время разбора, как требуется в Haskell 98 (но не в Haskell 2010). Например, согласно отчёту Haskell 98, следующее выражение законно:
let x = 42 in x == 42 == True
и разбирается как:
(let x = 42 in x == 42) == True
потому что, согласно отчёту, выражение
let«распространяется как можно дальше вправо». Поскольку оно не может распространиться дальше второй знака равенства без возникновения ошибки разбора (==не является фиктивным), выражениеletдолжно завершиться там. GHC просто поглощает всё выражение, разбирая его так:(let x = 42 in x == 42 == True)
16.1.1.3. Выражения и шаблоны
По умолчанию GHC делает некоторые программы немного более определёнными, чем они должны быть. Например, рассмотрите
f :: [a] -> b -> b f [] = error "urk" f (x:xs) = \v -> v main = print (f [] `seq` True)
Это должно вызвать error, но на самом деле выводит True. Причина: GHC расширяет f до
f :: [a] -> b -> b f [] v = error "urk" f (x:xs) v = v
Для большинства программ это достаточно улучшает эффективность, чтобы быть включённым, и плохо только в редких случаях. Чтобы подавить эту оптимизацию, используйте -fpedantic-bottoms.
16.1.1.4. Шаблоны с ошибками
После предложения MonadFail Proposal (MFP), блоки do-нотации, содержащие шаблон с ошибками, требуют ограничения MonadFail.
Например
mayFail :: (MonadIO m) => m () mayFail = do (Just value) <- fetchData putStrLn value
Выдаст предупреждение
• Could not deduce (MonadFail m)
arising from a do statement
with the failable pattern ‘(Just x)’
from the context: MonadIO m
bound by the type signature for:
mayFail :: forall (m :: * -> *). MonadIO m => m ()
И действительно, так как класс Monad больше не имеет метода fail, нам нужно явно добавить (MonadFail m) к ограничениям функции.
16.1.1.5. Проверка типов рекурсивных групп связываний
Отчёт Haskell определяет, что группа связываний (на верхнем уровне или в let или where) должна быть отсортирована в сильносвязанные компоненты, а затем проверка типов выполняется в порядке зависимости (Отчёт Haskell, раздел 4.5.1). При проверке типов каждой группы все связующие этой группы, имеющие явную сигнатуру типа, помещаются в среду типов со специфицированным полиморфным типом, а все остальные — мономорфные до обобщения группы (Отчёт Haskell, раздел 4.5.2).
Следуя предложению Марка Джонса в его работе Typing Haskell in Haskell, GHC реализует более общую схему. В GHC анализ зависимостей игнорирует ссылки на переменные, которые имеют явную сигнатуру типа. В результате этого уточнённого анализа зависимостей группы зависимостей становятся меньше, и больше связываний будут проверены на типы. Например, рассмотрим:
f :: Eq a => a -> Bool f x = (x == x) || g True || g "Yes" g y = (y <= y) || f True
Это отклоняется Haskell 98, но в схеме Джонса определение g проверяется на тип в первую очередь, отдельно от определения f, потому что ссылка на f в правой части g игнорируется анализом зависимостей. Затем тип g обобщается, чтобы получить
g :: Ord a => a -> Bool
Теперь определение f проверяется на тип, с этим типом для g в среде типов.
Тот же усовершенствованный анализ зависимостей также позволяет сигнатурам типов взаимно рекурсивных функций иметь разные контексты, что незаконно в Haskell 98 (Раздел 4.5.2, последнее предложение). GHC настаивает только на том, чтобы сигнатуры типов уточнённой группы имели одинаковые сигнатуры; на практике это означает, что только переменные, связанные одним и тем же шаблоном связи, должны иметь одинаковые контексты. Например, это нормально:
f :: Eq a => a -> Bool f x = (x == x) || g True g :: Ord a => a -> Bool g y = (y <= y) || f True