16.1.1.4. Шаблоны, допускающие отказ
Начиная с предложения MonadFail (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).
Следуя предложению Марка Джонса из его статьи «Типизация Haskell на 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
16.1.1.6. Заголовки модулей по умолчанию с -main-is
Отчёт Haskell 2010 указывает в <https://www.haskell.org/onlinereport/haskell2010/haskellch5.html#x11-990005.1>, что
- «Допускается сокращённая форма модуля, состоящая только из тела модуля,
-
если она используется, предполагается, что заголовок имеет вид module Main(main) where».
Параметр -main-is в GHC можно использовать, чтобы изменить имя точки входа верхнего уровня с main на любую другую переменную. При компиляции главного модуля, если параметр -main-is использовался для переименования точки входа по умолчанию, GHC также укажет альтернативное имя в списке экспорта по умолчанию.
Рассмотрим следующую программу:
-- file: Main.hs
program :: IO ()
program = return ()
GHC успешно скомпилирует этот модуль с ghc -main-is Main.program Main.hs, поскольку список экспорта по умолчанию будет содержать program вместо main, как обычно требует Отчёт Haskell.
Это изменение применяется только к главному модулю. Другие модули по-прежнему будут экспортировать main из списка экспорта по умолчанию независимо от флага -main-is. Это позволяет использовать -main-is с существующими модулями, которые экспортируют main через список экспорта по умолчанию, даже если -main-is указывает на другую точку входа, как в этом примере (скомпилированном с -main-is MainWrapper.program).
-- file MainWrapper.hs
module MainWrapper where
import Main
program :: IO ()
program = putStrLn "Redirecting..." >> main
-- file Main.hs
main :: IO ()
main = putStrLn "I am main."
16.1.1.7. Система модулей и интерфейсные файлы
GHC требует использования файлов hs-boot для разрыва рекурсивных циклов между взаимно рекурсивными модулями, как описано в разделе Взаимно рекурсивные модули и файлы hs-boot. Это скорее недостаток, чем ошибка: в Отчёте Haskell сказано (раздел 5.7)
«В зависимости от используемой реализации Haskell для раздельной компиляции взаимно рекурсивных модулей может потребоваться дополнительная информация в импортируемых модулях, чтобы на них можно было ссылаться до их компиляции. Для обработки взаимной рекурсии могут понадобиться явные сигнатуры типов для всех экспортируемых значений. Точные детали раздельной компиляции в этом Отчёте не определяются».