Spec-Zone.ru › Haskell 9

5.8. Имена файлов и отдельная компиляция

В данном разделе описывается, какие файлы ожидает найти GHC, какие файлы он создаёт, где хранятся эти файлы и какие параметры влияют на это поведение.

Конвенции именования путей различаются в зависимости от системы. В частности, разделитель каталогов — “/” в системах Unix и “\” в системах Windows. В следующих разделах мы будем последовательно использовать “/” в качестве разделителя каталогов; замените его соответствующим символом для вашей системы.

5.8.1. Файлы исходного кода Haskell

Каждый модуль Haskell должен храниться в отдельном файле.

Обычно имя файла должно совпадать с именем модуля, заменяя точки в имени модуля разделителями каталогов. Например, в системе Unix модуль A.B.C должен храниться в файле A/B/C.hs, относительно некоторого базового каталога. Если модуль не будет импортирован другим модулем (например, Main), то вы можете использовать любое имя файла для него.

GHC предполагает, что исходные файлы имеют кодировку ASCII или UTF-8, другие кодировки не распознаются. Однако невалидные последовательности UTF-8 будут игнорироваться в комментариях, поэтому можно использовать другие кодировки, такие как Latin-1, при условии, что некомментируемый исходный код использует только ASCII.

5.8.2. Файлы вывода

При компиляции исходного файла GHC обычно генерирует два файла: файл объекта и файл интерфейса.

Файл объекта, который обычно имеет суффикс .o , содержит скомпилированный код для модуля.

Файл интерфейса, который обычно имеет суффикс .hi , содержит информацию, необходимую GHC для компиляции других модулей, зависящих от данного модуля. Он содержит такие данные, как типы экспортированных функций, определения типов данных и так далее. Он хранится в бинарном формате, поэтому не пытайтесь его прочитать; используйте вместо этого параметр --show-iface ⟨file⟩ (см. Другие параметры, относящиеся к файлам интерфейса).

Вы должны рассматривать файл объекта и файл интерфейса как пару, поскольку файл интерфейса представляет собой, в некотором смысле, читаемый компилятором описание содержимого файла объекта. Если по какой-либо причине файл интерфейса и файл объекта разойдутся, компилятор может сделать предположения о файле объекта, которые не будут верны; в таком случае могут возникнуть проблемы. По этой причине рекомендуется хранить файлы объектов и интерфейсов в одном месте (GHC делает это по умолчанию, но можно изменить настройки, как мы объясним вскоре).

Каждый модуль имеет определённое имя модуля, определённое в его исходном коде (module A.B.C where ...).

Имя файла объекта, сгенерированного GHC, выводится по следующим правилам, где ⟨osuf⟩ — суффикс файла объекта (это можно изменить параметром -osuf ).

  • Если параметр -odir не указан (по умолчанию), имя файла объекта выводится из имени исходного файла (игнорируя имя модуля) путём замены суффикса на ⟨osuf⟩.
  • Если параметр -odir ⟨dir⟩ указан, имя файла объекта будет ⟨dir⟩/⟨mod⟩.⟨osuf⟩, где ⟨mod⟩ — имя модуля с точками, заменёнными на слэши. GHC будет молча создавать необходимую структуру каталогов под ⟨dir⟩, если она ещё не существует.

Имя файла интерфейса выводится по тем же правилам, за исключением того, что суффикс — ⟨hisuf⟩ (.hi по умолчанию) вместо ⟨osuf⟩, а соответствующие параметры — -hidir ⟨dir⟩ и -hisuf ⟨suffix⟩ вместо -odir ⟨dir⟩ и -osuf ⟨suffix⟩ соответственно.

Например, если GHC компилирует модуль A.B.C в файле src/A/B/C.hs, без флагов -odir или -hidir, файл интерфейса будет помещён в src/A/B/C.hi, а файл объекта — в src/A/B/C.o.

Для любого импортируемого модуля GHC требует, чтобы имя модуля в инструкции импорта точно совпадало с именем модуля в файле интерфейса (или исходном файле), найденном с помощью стратегии, указанной в Пути поиска. Это означает, что для большинства модулей имя исходного файла должно совпадать с именем модуля.

Однако, обратите внимание, что разумно иметь модуль Main в файле с именем foo.hs, но это работает только потому, что GHC никогда не должен искать интерфейс для модуля Main (потому что он никогда не импортируется). Поэтому можно иметь несколько модулей Main в отдельных исходных файлах в одном каталоге, и GHC не запутается.

В режиме пакетной компиляции имя файла объекта также может быть переопределено с помощью параметра -o ⟨file⟩, а имя файла интерфейса может быть указано непосредственно с помощью параметра -ohi ⟨file⟩.

5.8.3. Пути поиска

В вашей программе вы импортируете модуль Foo с помощью import Foo. В режиме --make или GHCi GHC будет искать исходный файл для Foo и произведёт его компиляцию в первую очередь. Без --make GHC будет искать файл интерфейса для Foo, который должен был быть создан в ходе предыдущей компиляции Foo.

Стратегия поиска исходных файлов следующая: GHC хранит список каталогов, называемый путём поиска. Для каждого из этих каталогов он пытается добавить ⟨basename⟩.⟨extension⟩ к каталогу и проверяет, существует ли файл. Значение ⟨basename⟩ — имя модуля с точками, заменёнными разделителями каталогов (”/” или “\\", в зависимости от системы), а ⟨extension⟩ — суффикс исходного файла (hs, lhs) если мы находимся в режиме --make или GHCi.

При поиске файлов интерфейсов в режиме -c мы ищем файлы интерфейсов в -hidir, если он задан. В противном случае используется та же стратегия, что и для исходных файлов, для поиска файла интерфейса.

Например, предположим, что путь поиска содержит каталоги d1, d2, и d3, и мы находимся в режиме --make, ищем исходный файл для модуля A.B.C. GHC будет искать в d1/A/B/C.hs, d1/A/B/C.lhs, d2/A/B/C.hs, и так далее.

По умолчанию путь поиска содержит один каталог: “.” (т.е. текущий каталог). Следующие параметры могут быть использованы для добавления или изменения содержимого пути поиска:

-i⟨dir⟩[:⟨dir⟩]*

Этот параметр добавляет список каталогов, разделённых двоеточием, к пути поиска.

-i

сбрасывает путь поиска до пустого.

Это не вся история: GHC также ищет модули в предварительно скомпилированных библиотеках, известных как пакеты. Подробности см. в разделе о пакетах (Пакеты).

5.8.4. Перенаправление выходных данных компиляции

-o ⟨file⟩

Выходные данные компилятора GHC обычно записываются в файл .hc, .o, и т.д., в зависимости от последней фазы компиляции. Опция -o file перенаправляет выходные данные последней фазы в файл ⟨file⟩.

Примечание

Эта «функция» может быть неинтуитивной: ghc -C -o foo.o foo.hs поместит промежуточный C-код в файл foo.o, несмотря на название!

Эта опция чаще всего используется при создании исполняемого файла для задания имени исполняемого файла. Например:

ghc -o prog --make Main

скомпилирует программу, начиная с модуля Main и поместит исполняемый файл в prog.

Примечание: в Windows, если результатом является исполняемый файл, расширение «.exe» добавляется, если указанное имя файла не содержит расширения. Таким образом

ghc -o foo Main.hs

скомпилирует и слинкует модуль Main.hs и поместит полученный исполняемый файл в foo.exe (а не в foo).

Если используется ghc --make и не используется -o, имя исполняемого файла, выбранное GHC, будет основано на имени файла, содержащем модуль Main. Обратите внимание, что с GHC модуль Main не обязательно помещать в файл Main.hs. Следовательно, оба

ghc --make Prog

и

ghc --make Prog.hs

произведут Prog (или Prog.exe в Windows).

-dyno ⟨file⟩

При использовании -dynamic-too, опция -dyno ⟨suffix⟩ является аналогом -o. Она перенаправляет динамический вывод в файл ⟨file⟩.

-odir ⟨dir⟩

Перенаправляет файлы объектов в директорию ⟨dir⟩. Например:

$ ghc -c parse/Foo.hs parse/Bar.hs gurgle/Bumble.hs -odir `uname -m`

Файлы объектов, Foo.o, Bar.o, и Bumble.o будут помещены в поддиректорию, названную в соответствии с архитектурой исполняющей машины (x86, mips, и т.д.).

Обратите внимание, что опция -odir не влияет на расположение файлов интерфейса; для этого используйте опцию -hidir. В приведенном выше примере они всё ещё будут помещены в parse/Foo.hi, parse/Bar.hi, и gurgle/Bumble.hi.

Также обратите внимание, что при инкрементной компиляции эта директория используется GHC для поиска файлов объектов из предыдущих сборок.

-ohi ⟨file⟩

Вывод интерфейса может быть перенаправлен в другой файл bar2/Wurble.iface с помощью опции -ohi bar2/Wurble.iface (не рекомендуется).

Предупреждение

Если вы перенаправите файл интерфейса в место, где GHC не может его найти, тогда механизм проверки повторной компиляции может запутаться (как минимум, вы не получите избежания повторной компиляции). Рекомендуется использовать сочетание опций -hidir и -hisuf вместо этого, если это возможно.

Чтобы вообще не генерировать интерфейс, вы можете использовать эту опцию для перенаправления интерфейса в «пустоту»: -ohi /dev/null, например.

-dynohi ⟨file⟩

При использовании -dynamic-too, опция -dynohi ⟨file⟩ является аналогом -ohi. Она перенаправляет вывод динамического интерфейса в файл ⟨file⟩.

-hidir ⟨dir⟩

Перенаправляет все сгенерированные файлы интерфейсов в директорию ⟨dir⟩ вместо стандартного расположения.

Обратите внимание, что при инкрементной компиляции (с помощью ghc --make или ghc -c) эта директория используется GHC для поиска файлов интерфейсов.

-hiedir ⟨dir⟩

Перенаправляет все сгенерированные расширенные файлы интерфейсов в директорию ⟨dir⟩ вместо стандартного расположения.

Обратите внимание, что при инкрементной компиляции (с помощью ghc --make или ghc -c) эта директория используется GHC для поиска расширенных файлов интерфейсов.

-stubdir ⟨dir⟩

Перенаправляет все сгенерированные файлы заглушек FFI в директорию ⟨dir⟩. Файлы заглушек генерируются, когда в исходном коде Haskell присутствует объявление foreign export или foreign import "&wrapper" (см. Использование внешнего экспорта и внешнего импорта ccall "обёртки" с GHC). Опция -stubdir ведет себя точно так же, как опции -odir и -hidir относительно иерархических модулей.

-dumpdir ⟨dir⟩

Перенаправляет все файлы дампов в директорию ⟨dir⟩. Файлы дампов генерируются, когда используется -ddump-to-file с другими флагами -ddump-*.

-outputdir ⟨dir⟩

Опция -outputdir является сокращением для комбинации -odir ⟨dir⟩, -hidir ⟨dir⟩, -hiedir ⟨dir⟩, -stubdir ⟨dir⟩ и -dumpdir ⟨dir⟩.

-osuf ⟨suffix⟩

Опция -osuf ⟨suffix⟩ изменит расширение файлов объектов на то, что вы укажете. Мы используем это при компиляции библиотек, чтобы файлы объектов для профилирующих версий библиотек не перезаписывали обычные.

-dynosuf ⟨suffix⟩

При использовании -dynamic-too, опция -dynosuf ⟨suffix⟩ является аналогом -osuf. Она изменяет расширение файлов динамических объектов.

-hisuf ⟨suffix⟩

Аналогично, опция -hisuf ⟨suffix⟩ изменит расширение файла интерфейса для несистемных файлов (см. Другие опции, связанные с файлами интерфейса).

Этот механизм особенно полезен, если вы хотите скомпилировать программу как с профилированием, так и без него, в одной директории. Вы можете использовать:

ghc ...

для получения обычной версии и

ghc ... -osuf prof.o -hisuf prof.hi -prof -fprof-auto

для получения профилированной версии.

-dynhisuf ⟨suffix⟩

При использовании -dynamic-too, опция -dynhisuf ⟨suffix⟩ является аналогом -hisuf. Она изменяет расширение файла динамического интерфейса.

-hiesuf ⟨suffix⟩

Опция -hiesuf ⟨suffix⟩ изменит расширение файла расширенного интерфейса на указанное вами значение.

-hcsuf ⟨suffix⟩

И наконец, опция -hcsuf ⟨suffix⟩ изменит расширение файла промежуточного C-кода, сгенерированного компилятором.

5.8.5. Сохранение промежуточных файлов

Следующие параметры полезны для сохранения (или не сохранения) определённых промежуточных файлов, которые обычно GHC удаляет после компиляции:

-keep-hc-file
-keep-hc-files

Сохранять промежуточные файлы .hc при выполнении компиляций .hs в .o через C (Примечание: файлы .hc генерируются только незарегистрированными компиляторами).

-keep-hi-files

Сохранять промежуточные файлы .hi. Это значение по умолчанию. Вы можете использовать -no-keep-hi-files, если вас не интересуют файлы .hi.

-keep-hscpp-file
-keep-hscpp-files

Сохранять результаты фазы предварительной обработки CPP в виде файлов .hscpp. Файл .hscpp создаётся только в том случае, если модуль компилируется и использует предварительную обработку C.

-keep-llvm-file
-keep-llvm-files
Подразумевает:

-fllvm

Сохранять промежуточные файлы .ll при выполнении компиляций .hs в .o через LLVM (Примечание: файлы .ll не генерируются при использовании нативного генератора кода, вам может потребоваться использовать -fllvm для принудительного их создания).

-keep-o-files

Сохранять промежуточные файлы .o. Это значение по умолчанию. Вы можете использовать -no-keep-o-files, если вас не интересуют файлы .o.

-keep-s-file
-keep-s-files

Сохранять промежуточные файлы .s.

-keep-tmp-files

Указывает драйверу GHC не удалять временные файлы, которые он обычно сохраняет в /tmp (или, возможно, в другом месте; см. Перенаправление временных файлов). Запуск GHC с -v покажет вам временные файлы, сгенерированные во время процесса.

5.8.6. Перенаправление временных файлов

-tmpdir ⟨dir⟩

Если у вас проблемы из-за недостатка места в /tmp (или в том месте, куда ваша система предполагает сохранение временных файлов), вы можете использовать параметр -tmpdir ⟨dir⟩ для указания альтернативной директории. Например, -tmpdir . указывает на сохранение временных файлов в текущей рабочей директории.

В качестве альтернативы используйте переменную окружения TMPDIR. Установите её в имя директории, в которую должны сохраняться временные файлы. GCC и другие программы также учтут переменную TMPDIR.

5.8.7. Другие параметры, относящиеся к файлам интерфейса

-ddump-hi

Выводит новый интерфейс в стандартный вывод.

-ddump-hi-diffs

Компилятор не перезаписывает существующий файл интерфейса .hi, если новый файл такой же, как и старый; это удобно для make. Когда интерфейс меняется, полезно получать информацию об этом. Параметр -ddump-hi-diffs позволит GHC сообщить о различиях между старым и новым файлами интерфейса .hi.

-ddump-minimal-imports

Выводит в файл M.imports (где ⟨M⟩ — имя компилируемого модуля) минимальный набор директив импорта. Директория, в которой создаются файлы .imports, может быть задана с помощью параметра -dumpdir ⟨dir⟩.

Вы можете безопасно заменить все директивы импорта в M.hs директивными импортами из соответствующего файла .imports. Зачем это нужно? Потому что «минимальные» импорты (а) импортируют всё явно, по имени, и (б) импортируют только то, что требуется. Ручное поддержание этого свойства может быть утомительным, поэтому данный параметр предназначен для уменьшения этой работы.

--show-iface ⟨file⟩

где ⟨file⟩ — имя файла интерфейса, выводит содержимое этого файла интерфейса в удобочитаемом формате. См. Режимы работы.

5.8.8. Параметры, относящиеся к расширенным файлам интерфейса

GHC собирает богатую информацию о файле Haskell-исходника при его компиляции. Расширенные файлы интерфейса — это способ сохранения части этой информации на диске, чтобы внешние инструменты, такие как IDE, могли избежать повторного парсинга, проверки типов и переименования. Эти файлы содержат:

  • упрощённое AST

    • узлы аннотированы позициями и типами в исходном коде
    • идентификаторы аннотированы информацией о области видимости
  • необработанные байты исходного файла Haskell

API GHC предоставляет функции для чтения и записи этих файлов.

-fwrite-ide-info

Записывает расширенные файлы интерфейса вместе с обычными файлами интерфейса. Как и обычные файлы интерфейса, GHC проверяет повторную компиляцию, чтобы обнаружить устаревшие или отсутствующие расширенные файлы интерфейса.

-fvalidate-ide-info

Выполняет ряд проверок корректности и линтеров на расширенных файлах интерфейса, которые записываются. Они включают в себя проверку таких свойств, как отсутствие переменных за пределами ожидаемой области видимости.

Формат, в котором GHC в настоящее время хранит свою проверенную AST, делает сбор типов для некоторых узлов выражений дорогостоящим. Для повышения производительности GHC пропускает эти узлы, поэтому не ожидайте наличия информации о типе для всех узлов выражений. См. #16233 для более подробной информации.

5.8.9. Проверяющий перекомпиляцию

-fforce-recomp

Отключить проверку перекомпиляции (которая включена по умолчанию). Проверка перекомпиляции обычно останавливает компиляцию на ранней стадии, оставляя существующий файл .o на месте, если можно определить, что модуль не нуждается в перекомпиляции.

-fignore-optim-changes
-fignore-hpc-changes

В старые времена GHC сравнивал недавно сгенерированный файл .hi с предыдущей версией; если они были идентичны, он оставлял старый файл без изменений и не изменял его дату модификации. В результате импортеры модуля с неизменённым выходным файлом .hi не перекомпилировались.

Это больше не работает. Предположим, модуль C импортирует модуль B, а B импортирует модуль A. Таким образом, изменения в модуле A могут потребовать перекомпиляции модуля C, и поэтому при изменениях в A.hi мы должны проверить, нужно ли перекомпилировать C. Однако зависимости C будут перечислять только B.hi, а не A.hi, и некоторые изменения в A (изменение определения функции, которая появляется в инлайнинге функции, экспортированной B, например) могут теоретически не повлиять на B.hi ни на йоту. Итак, теперь…

GHC вычисляет отпечаток (фактически хэш MD5) каждого файла интерфейса и каждого объявления внутри файла интерфейса. Он также хранит в каждом файле интерфейса список отпечатков всего, что он использовал при последней компиляции файла. Если хэш MD5 исходного файла, сохранённого в файле .hi, не изменился, дата модификации файла .o больше или равна дате файла .hi, и проверка перекомпиляции включена, GHC будет умным. Он сравнивает отпечатки того, что ему нужно сейчас, с отпечатками того, что ему нужно было в прошлый раз (полученными из файла интерфейса компилируемого модуля); если они все одинаковы, он останавливает компиляцию на ранней стадии, заявив: «Перекомпиляция НЕ требуется». Какая красота!

Вы можете прочитать о том, как всё это работает, в комментариях GHC.

5.8.9.1. Перекомпиляция для Template Haskell и плагинов

Проверка перекомпиляции становится немного сложнее при использовании Template Haskell или плагинов. Обе эти функции выполняют код во время компиляции, поэтому, если какой-либо из исполняемых кодов изменится, необходимо перекомпилировать модуль. Рассмотрим верхний уровень вставки:

main = $(foo bar [| () |])

При компиляции модуля foo bar [| () |] будет вычислено и полученный код помещен в программу. Зависимости выражения вычисляются и сохраняются во время компиляции модуля. При записи файла интерфейса создаются дополнительные зависимости от зависимостей файла объекта выражения. Например, если foo из модуля A и bar из модуля B, модуль теперь будет зависеть от A.o и B.o, если какой-либо из них изменится, модуль будет перекомпилирован.

5.8.10. Взаимно рекурсивные модули и файлы hs-boot

GHC поддерживает компиляцию взаимно рекурсивных модулей. В этом разделе объясняется как.

Каждый цикл в графе импорта модулей должен быть разорван файлом hs-boot. Предположим, что модули A.hs и B.hs являются файлами Haskell исходного кода, таким образом:

module A where
    import B( TB(..) )

    newtype TA = MkTA Int

    f :: TB -> TA
    f (MkTB x) = MkTA x

module B where
    import {-# SOURCE #-} A( TA(..) )

    data TB = MkTB !Int

    g :: TA -> TB
    g (MkTA x) = MkTB x

Здесь A импортирует B, но B импортирует A с пragma {-# SOURCE #-}, что разрывает циклическую зависимость. Каждый цикл в графе импорта модулей должен быть разорван импортом {-# SOURCE #-}; или, эквивалентно, граф импорта модулей должен быть ациклическим, если импорты {-# SOURCE #-} игнорируются.

Для каждого модуля A.hs, который {-# SOURCE #-}-импортируется таким образом, должен существовать файл исходного кода A.hs-boot. Этот файл содержит сокращённую версию A.hs, таким образом:

module A where
    newtype TA = MkTA Int

Чтобы скомпилировать эти три файла, выполните следующие команды:

ghc -c A.hs-boot    -- Produces A.hi-boot, A.o-boot
ghc -c B.hs         -- Consumes A.hi-boot, produces B.hi, B.o
ghc -c A.hs         -- Consumes B.hi, produces A.hi, A.o
ghc -o foo A.o B.o  -- Linking the program

Здесь следует отметить несколько моментов:

  • Файл A.hs-boot является файлом исходного кода, написанного программистом. Он должен находиться в той же директории, что и его родительский файл исходного кода A.hs. В настоящее время, если вы используете файл исходного кода в формате «литературного» файла A.lhs, вам также необходимо использовать файл «литературного» файла boot, A.lhs-boot; и наоборот.
  • Файл hs-boot компилируется GHC, точно так же, как файл hs:

    ghc -c A.hs-boot
    

    При компиляции файла hs-boot A.hs-boot выполняется проверка на ошибки области видимости и типов. При компиляции родительского модуля A.hs, эти два файла сравниваются, и об ошибке сообщается, если они несовместимы.

  • Точно так же, как компиляция A.hs создаёт файл интерфейса A.hi и файл объектного кода A.o, так и компиляция A.hs-boot создаёт файл интерфейса A.hi-boot и псевдо-файл объектного кода A.o-boot:

    • Псевдо-файл объектного кода A.o-boot пустой (не нужно его линкеровать!), но очень полезен при использовании Makefile, для записи времени последнего обновления A.hi-boot (см. Использование make).
    • Файл hi-boot, созданный при компиляции файла hs-boot имеет тот же формат машинного бинарного файла, что и любой другой файл интерфейса, созданный GHC (например, B.hi). Вы можете отобразить его содержимое с помощью ghc --show-iface. Если вы укажите директорию для файлов интерфейса, флаг -hidir, то это также повлияет на файлы hi-boot.
  • Если файлы hs-boot считаются отличными от файлов исходного кода, и если импорт {-# SOURCE #-} считается ссылкой на файл hs-boot, то граф импорта модулей не должен содержать циклов. Команда ghc -M сообщит об ошибке, если цикл будет обнаружен.
  • Модуль M, который {-# SOURCE #-}-импортируется в программе, обычно также импортируется и другим способом. Если нет, ghc --make автоматически добавляет M в набор модулей, которые он пытается скомпилировать и слинковать, чтобы гарантировать, что реализация M включена в окончательную программу.

Файл hs-boot должен содержать только минимальную информацию, необходимую для запуска процесса загрузки. Например, он не должен содержать объявления всего, что экспортирует модуль A, а только то, что требуется импортирующим модулем(ами) A рекурсивно.

Файл hs-boot написан на подмножестве языка Haskell:

  • Заголовок модуля (включая список экспорта) и операторы импорта точно такие же, как в Haskell, и такие же правила области видимости. Таким образом, чтобы упомянуть тип или класс, не из Prelude, необходимо импортировать его.
  • Не должно быть объявлений значений, но могут быть сигнатуры типов для значений. Например:

    double :: Int -> Int
    
  • Объявления фиктивности такие же, как в Haskell.
  • Объявления синонимов типов такие же, как в Haskell.
  • Объявления открытых типов и семейств данных такие же, как в Haskell.
  • Объявление закрытого семейства данных может быть представлено полностью (где все уравнения должны соответствовать исходному модулю), или оно может быть представлено абстрактно, используя синтаксис where .. (тем самым опуская уравнения), как в следующем примере:

    type family ClosedFam a where ..
    

    Знак .. должен быть написан буквально — вы должны написать две точки в своём файле. Обратите внимание, что where фраза всё ещё необходима, чтобы отличить закрытые семейства от открытых. Если вы даёте любые уравнения закрытого семейства, вы должны указать все из них в том же порядке, в котором они появляются в сопровождающем файле Haskell.

  • Объявление типа данных может быть дано полностью, точно так же, как в Haskell, или оно может быть дано абстрактно, опуская знак «=» и всё, что за ним следует. Например:

    data T a b
    

    В исходной программе это объявило бы TA без конструкторов (расширение GHC: см. Типы данных без конструкторов), но в файле hi-boot это означает «Я не знаю и не забочусь о том, что это за конструкторы». Это наиболее распространённая форма объявления типа данных, потому что её легко использовать. Вы можете также написать конструкторы, но, если вы это сделаете, вы должны написать их точно так же, как и в их реальном определении.

    Если вы не пишете конструкторы, вам может потребоваться указать аннотацию типа (Явное указание типов), чтобы сообщить GHC вид переменной типа, если он не «*». (В исходных файлах это вычисляется из того, как переменная типа используется в конструкторах.) Например:

    data R (x :: * -> *) y
    

    Вы не можете использовать deriving на объявлении типа данных; вместо этого напишите объявление instance.

  • Объявления классов могут быть представлены полностью, точно так же, как в Haskell, или могут быть представлены абстрактно, опуская всё кроме заголовка экземпляра: никаких суперклассов, никаких методов класса, никаких связанных типов. Однако, если класс имеет ::extension::FunctionalDependencies, эти значения в файле hs-boot должны быть такими же.

    Если объявление класса представлено полностью, всё объявление класса должно быть идентичным, за исключением переименования переменных типа, связанных с заголовком класса. Это означает:

    • Заголовок класса должен быть таким же.
    • Контекст класса должен быть таким же, с учётом упрощения ограничений.
    • Если есть ::extension::FunctionalDependencies, эти значения должны быть такими же.
    • Порядок, имена и типы методов класса должны быть такими же.
    • Арность и типы любых связанных типов должны быть такими же.
    • Методы по умолчанию, а также сигнатуры по умолчанию (см. ::extension::DefaultSignatures) должны быть предоставлены для тех же методов и должны быть одинаковыми.
    • Объявления по умолчанию для связанных типов должны быть предоставлены для тех же типов и должны быть одинаковыми.

    Чтобы объявить класс без методов в файле hs-boot, он должен иметь суперкласс. Если у класса нет ограничений суперкласса, добавьте пустое ограничение, например:

    class () => C a
    

    Это полное объявление класса, а не абстрактное объявление, в котором были пропущены методы.

  • Вы можете включать объявления экземпляров так же, как в Haskell; но опустите часть «where».
  • По умолчанию роль параметров абстрактного типа сейчас представительская. (Абстрактный тип — это тип без указанных конструкторов.) Чтобы получить другую роль, используйте аннотацию роли. (См. Роли.)

5.8.11. Сигнатуры модулей

GHC 8.2 поддерживает сигнатуры модулей (файлы hsig), которые позволяют вам написать сигнатуру вместо реализации модуля, отложив выбор реализации до более позднего момента. Эта функция не предназначена для использования без Cabal; эта справка будет сосредоточена на синтаксисе и семантике сигнатур.

Начнём с примера. Предположим, у вас есть модуль A, который использовал некоторые операции со строками. Используя обычные импорты модулей, вы могли бы выбрать только конкретную реализацию строк:

module Str where
    type Str = String

    empty :: Str
    empty = ""

    toString :: Str -> String
    toString s = s

module A where
    import Str
    z = toString empty

Заменив Str.hs сигнатурой Str.hsig, A (и любые другие модули в этом пакете) теперь параметризованы реализацией строк:

signature Str where
    data Str
    empty :: Str
    toString :: Str -> String

Мы можем проверить тип A по отношению к этой сигнатуре, или мы можем экземпляризировать Str с модулем, который предоставляет следующие объявления. Обратитесь к документации Cabal для более подробного обсуждения того, как создавать экземпляры сигнатур.

Сигнатуры модулей фактически состоят из двух тесно связанных функций:

  • Возможность определения файла hsig, содержащего определения типов и сигнатуры типов для значений, которые могут использоваться модулями, импортирующими сигнатуру, и должны быть предоставлены конечным реализующим модулем, и
  • Возможность унаследовать необходимые сигнатуры из пакетов, на которые мы опираемся, объединяя сигнатуры в одну объединённую сигнатуру, которая отражает требования любого локально определённого сигнатуры, а также требования наших зависимостей.

Файл сигнатуры обозначается файлом hsig; каждый необходимый файл сигнатуры должен иметь файл hsig (даже если это пустой файл), включая необходимые сигнатуры, унаследованные от зависимостей. Сигнатуры могут быть импортированы с помощью обычного оператора import Sig.

Файлы hsig написаны на варианте Haskell, аналогичном файлам hs-boot, но с небольшими изменениями:

  • Заголовок подписи — signature A where ... (вместо обычного module A where ...).
  • Инструкции импорта и правила области видимости точно такие же, как в Haskell. Для упоминания типа или класса, не относящегося к Prelude, необходимо импортировать его.
  • В отличие от обычных модулей, определённые сущности подписи включают в себя не только те, что написаны в локальном файле hsig, но и те, что унаследованы из подписей (как выводится из флагов -package-id ⟨unit-id⟩). Эти сущности не учитываются при проверке типов локального файла hsig, но доступны для импорта любым модулем или подписью, которые импортируют подпись. Исключением из этого правила является список экспорта, описанный ниже.

    Если объявление встречается в нескольких унаследованных подписях, они будут слиты вместе. Для значений мы требуем, чтобы типы из обеих подписей точно совпадали; однако, другие объявления могут сливаться более интересными способами. Операция слияния в этих случаях имеет эффект текстовой замены всех вхождений старого имени ссылкой на новое, объединённое объявление. Например, если у нас есть следующие две подписи:

    signature A where
        data T
        f :: T -> T
    
    signature A where
        data T = MkT
        g :: T
    

    результирующая объединённая подпись будет:

    signature A where
        data T = MkT
        f :: T -> T
        g :: T
    
  • Если для подписи не указан список экспорта, экспортом подписи являются все её определённые сущности, объединённые с экспортом всех унаследованных подписей.

    Если вы хотите повторно экспортировать сущность из подписи, вы также должны включить module SigName экспорт, чтобы все сущности, определённые в подписи, были экспортированы. Например, следующий модуль экспортирует как f, так и Int из Prelude:

    signature A(module A, Int) where
        import Prelude (Int)
        f :: Int
    

    Повторные экспорты сливаются с локальными объявлениями; таким образом, указанная выше подпись успешно сливается с:

    signature A where
        data Int
    

    Единственно допустимая реализация такой подписи — это модуль, который повторно экспортирует ровно ту же сущность:

    module A (f, Int) where
        import Prelude (Int)
        f = 2 :: Int
    

    И наоборот, любая сущность, запрошенная подписью, может быть предоставлена повторным экспортом из реализующего модуля. Это отличается от файлов hs-boot , которые требуют, чтобы каждая сущность была определена локально в реализующем модуле.

  • GHC имеет экспериментальную поддержку утончения подписи, которая используется, когда у подписи есть явный список экспорта без экспорта самого модуля подписи. В этом случае список экспорта применяется к окончательному списку экспорта после слияния, в частности, вы можете ссылаться на сущности, которые не объявлены в теле локального файла hsig.

    Семантика в этом случае заключается в том, что множество необходимых сущностей определяется исключительно своим списком экспорта; если сущность не указана в списке экспорта, она не требуется. Мотивация этой функции заключается в том, чтобы позволить автору библиотеки предоставить универсальную подпись, содержащую тип каждой функции, которую кто-то может захотеть использовать, в то время как клиент сужает экспорт до тех, которые ему действительно необходимы. Например, предположим, что вы унаследовали подпись для строк, вы можете написать локальную подпись в таком виде, указав только необходимые сущности:

    signature Str (Str, empty, append, concat) where
        -- empty
    

    Здесь действуют несколько ограничений. Во-первых, запрещено экспортировать сущность, которая ссылается на локально определённый тип, который сам не экспортируется (GHC в этом случае сообщит об ошибке). Во-вторых, подписи, которые поступают из зависимостей, которые раскрывают модули, нельзя сужать таким образом (в конце концов, сама зависимость может потребовать сущность); эти требования экспортируются безусловно. Наконец, любые повторные экспорты должны ссылаться на модули, импортированные локальной подписью (даже если унаследованная подпись экспортировала модуль).

    Мы можем изменить синтаксис и семантику этой функции в будущем.

  • Объявления и типы из подписей зависимостей, которые будут объединены, не находятся в области видимости во время проверки типов файла hsig. Чтобы сослаться на любой такой тип, вы должны объявить его самостоятельно:

    -- OK, assuming we inherited an A that defines T
    signature A (T) where
        -- empty
    
    -- Not OK
    signature A (T, f) where
        f :: T -> T
    
    -- OK
    signature A (T, f) where
        data T
        f :: T -> T
    
  • Не должно быть объявлений значений, но могут быть сигнатуры типов для значений. Например, мы можем определить подпись:

    signature A where
        double :: Int -> Int
    

    Модуль, реализующий A, должен экспортировать функцию double с типом, тождественным типу в подписи. Обратите внимание, что это означает, что вы не можете реализовать double с помощью полиморфной функции double :: Num a => a -> a.

    Обратите внимание, что соответствие подписи проверяет, соответствует ли фиксированность, поэтому обязательно укажите фиксированность обычных идентификаторов, если вы намерены использовать их с обратными кавычками.

  • Фиксированность, синонимы типов, объявления открытых типов/семейств данных разрешены так же, как и в обычном Haskell.
  • Объявления закрытых семейств данных разрешены так же, как и в обычном Haskell. Они также могут быть заданы абстрактно, как в следующем примере:

    type family ClosedFam a where ..
    

    .. подразумевается буквально — вы должны написать две точки в своём файле. where заголовок отличает закрытые семейства от открытых.

  • Объявление типа данных может быть задано полностью, точно так же, как и в Haskell, или абстрактно, опуская знак «=» и всё, что следует за ним. Например:

    signature A where
        data T a b
    

    Абстрактные типы данных могут быть реализованы не только с объявлениями данных, но и с newtypes и синонимами типов (с ограничением, что синоним типа должен быть полностью эта-сведён, например, type T = ... , чтобы быть принятым). Например, все следующие являются допустимыми реализациями типа T выше:

    -- Algebraic data type
    data T a b = MkT a b
    
    -- Newtype
    newtype T a b = MkT (a, b)
    
    -- Type synonym
    data T2 a b = MkT2 a a b b
    type T = T2
    

    Объявления типов данных сливаются только с другими объявлениями типов данных, которые точно совпадают, за исключением абстрактных типов, которые могут сливаться с data, newtype или type объявлениями. Слияния с синонимами типов особенно полезны: предположим, что вы используете пакет строк, который оставил тип символов в строке неопределённым:

    signature Str where
        data Str
        data Elem
        head :: Str -> Elem
    

    Если вы локально определите подпись, которая указывает type Elem = Char, вы сможете теперь использовать head из унаследованной подписи так, как будто она возвращает Char.

    Если вы не пишете конструкторы, вам может потребоваться указать тип, чтобы сказать GHC, каковы типы переменных типа, если они не являются стандартными *. В отличие от обычных объявлений типов данных, возвращаемый тип абстрактного объявления данных может быть любым (в этом случае он, вероятно, будет реализован с помощью синонима типа). Это может быть использовано для обеспечения полиморфизма представления на этапе компиляции (в отличие от полиморфизма представления во время выполнения), как в этом примере:

    signature Number where
        import GHC.Types
        data Rep :: RuntimeRep
        data Number :: TYPE Rep
        plus :: Number -> Number -> Number
    

    Роли параметров типа подчиняются отношению подтипов phantom < representational < nominal: например, абстрактный тип с именованным параметром типа может быть реализован с использованием конкретного типа с параметром типа представления. Слияние учитывает это отношение подтипов (например, nominal сливается с representational в representational). Роли в подписях по умолчанию nominal, что обеспечивает максимальную гибкость со стороны исполнителя. Вам потребуется только явно указать аннотацию роли, если клиент подписи захочет принудить абстрактный тип в параметре типа (в этом случае вы должны явно указать representational). В отличие от обычных типов данных, мы не предполагаем, что абстрактные типы данных являются представительно инъективными: если у нас есть Coercible (T a) (T b), и T имеет роль nominal, это не подразумевает, что a ~ b.

  • Объявление класса может быть абстрактным или конкретным. Абстрактный класс — это класс без надклассов или методов класса:

    signature A where
        class Key k
    

    Его можно реализовать любым способом, с любым набором надклассов и методов; однако, модулям, зависящим от абстрактного класса, не разрешается определять экземпляры (по состоянию на GHC 8.2, это ограничение не проверяется, см. #13086). Эти объявления могут быть реализованы синонимами типов типа Constraint; это может быть полезно, если вы хотите параметризовать по ограничению в функциях. Например, с расширением ConstraintKinds этот синоним типа является допустимой реализацией указанной выше подписи:

    module A where
        type Key = Eq
    

    Конкретный класс определяет свои надклассы, методы, сигнатуры методов по умолчанию (но не их реализации) и псевдоним MINIMAL . В отличие от обычных классов Haskell, вам не нужно явно объявлять метод по умолчанию, чтобы сделать его необязательным по отношению к псевдониму MINIMAL .

    При слиянии объявлений классов мы требуем, чтобы надклассы и методы точно совпадали; однако, псевдонимы MINIMAL логически объединяются по «или», и метод с сигнатурой по умолчанию успешно сольётся с методом без неё.

  • Вы можете включать объявления экземпляров, как и в Haskell; просто опустите часть «где». Объявление экземпляра не обязательно должно быть реализовано напрямую; если экземпляр может быть выведен на основе экземпляров в среде, он считается реализованным. Например, следующая подпись:

    signature A where
        data Str
        instance Eq Str
    

    считается реализованной следующим модулем, так как существуют экземпляры Eq для [] и Char, которые можно объединить, чтобы сформировать экземпляр Eq [Char]:

    module A where
        type Str = [Char]
    

    В отличие от других объявлений, для которых в область видимости попадают только сущности, объявленные в файле подписи, экземпляры из реализации всегда попадают в область видимости, даже если они не были объявлены в файле подписи. Это означает, что модуль может пройти проверку типов по отношению к подписи, но не по отношению к соответствующей реализации. Вы можете избежать таких ситуаций, никогда не определяя сторонних экземпляров внутри пакета с подписями.

    Объявления экземпляров сливаются только в том случае, если их заголовки точно совпадают, поэтому возможно, что GHC сочтёт, что экземпляры в подписи пересекаются, даже если они реализуются непересекающимся образом. Если у вас возникают проблемы, сообщите нам.

  • Любые сторонние экземпляры, которые попадают в область видимости из-за импорта из подписи, безусловно, считаются в области видимости, даже если фактический реализующий модуль на самом деле не импортирует те же сторонние экземпляры.

Известные ограничения:

  • Синонимы шаблонов не поддерживаются.
  • Алгебраические типы данных, указанные в подписи, не могут быть реализованы с помощью синонимов шаблонов. См. #12717

5.8.12. Использование make

Достаточно просто настроить Makefile для использования с GHC, предполагая, что вы даёте исходным файлам те же имена, что и модулям. Таким образом:

HC      = ghc
HC_OPTS = -cpp $(EXTRA_HC_OPTS)

SRCS = Main.lhs Foo.lhs Bar.lhs
OBJS = Main.o   Foo.o   Bar.o

.SUFFIXES : .o .hs .hi .lhs .hc .s

cool_pgm : $(OBJS)
        rm -f $@
        $(HC) -o $@ $(HC_OPTS) $(OBJS)

# Standard suffix rules
.o.hi:
        @:

.lhs.o:
        $(HC) -c $< $(HC_OPTS)

.hs.o:
        $(HC) -c $< $(HC_OPTS)

.o-boot.hi-boot:
        @:

.lhs-boot.o-boot:
        $(HC) -c $< $(HC_OPTS)

.hs-boot.o-boot:
        $(HC) -c $< $(HC_OPTS)

# Inter-module dependencies
Foo.o Foo.hc Foo.s    : Baz.hi          # Foo imports Baz
Main.o Main.hc Main.s : Foo.hi Baz.hi   # Main imports Foo and Baz

Примечание

Сложные варианты make могут добиться того же более элегантно. В частности, правила шаблонов gmake позволяют написать более понятный вариант:

%.o : %.lhs
        $(HC) -c $< $(HC_OPTS)

Показанное нами должно работать с любым make.

Обратите внимание на простое правило .o.hi: оно записывает зависимость интерфейса (.hi) от исходного файла. Правило гласит, что файл .hi можно создать из файла .o, ничего не делая. Что верно.

Обратите внимание, что правила суффиксов повторяются дважды, один раз для обычных файлов исходного кода Haskell и один раз для файлов hs-boot (см. Взаимно рекурсивные модули и файлы hs-boot).

Также обратите внимание на межмодульные зависимости в конце файла Makefile, которые имеют вид

Foo.o Foo.hc Foo.s    : Baz.hi          # Foo imports Baz

Они сообщают make, что если у любого из Foo.o, Foo.hc или Foo.s дата последнего изменения раньше, чем у Baz.hi, то файл, нуждающийся в обновлении, должен быть обновлён. Для обновления make ищет правило для этого; одно из предыдущих правил суффиксов выполняет эту задачу достаточно хорошо. Эти зависимости могут быть сгенерированы автоматически ghc; см. Генерация зависимостей

5.8.13. Генерация зависимостей

Вручную вносить межмодульные зависимости вида Foo.o : Bar.hi в ваш Makefile довольно рискованно. Не волнуйтесь, GHC поддерживает автоматическую генерацию необходимых зависимостей. Добавьте следующее в ваш Makefile:

depend :
        ghc -M $(HC_OPTS) $(SRCS)

Теперь, прежде чем начать компиляцию и всякий раз, когда вы изменяете imports в вашей программе, выполните make depend перед тем, как выполнить make cool_pgm. Команда ghc -M добавит необходимые зависимости в ваш Makefile.

В общем, ghc -M Foo выполняет следующее. Для каждого модуля M в наборе Foo плюс всех его импортов (транзитивно), он добавляет в Makefile:

  • Строку, записывающую зависимость объектного файла от исходного файла.

    M.o : M.hs
    

    (или M.lhs если вы использовали это имя файла).

  • Для каждой декларации импорта import X в M, строку, записывающую зависимость M от X:

    M.o : X.hi
    
  • Для каждой декларации импорта import {-# SOURCE #-} X в M, строку, записывающую зависимость M от X:

    M.o : X.hi-boot
    

    (См. Взаимно рекурсивные модули и файлы hs-boot для подробностей о файлах интерфейса hi-boot.)

Если M импортирует несколько модулей, то будет несколько строк с M.o в качестве цели.

Нет необходимости перечислять все исходные файлы в качестве аргументов к команде ghc -M; ghc отслеживает зависимости, точно так же, как ghc --make (новая функция в GHC 6.4).

Обратите внимание, что ghc -M должен найти исходный файл для каждого модуля в графе зависимостей, чтобы он мог проанализировать декларации импорта и отследить зависимости. Поэтому все предварительно скомпилированные модули без исходных файлов должны принадлежать к пакетам [1].

По умолчанию ghc -M генерирует все зависимости и затем прикрепляет их к концу makefile (или Makefile если makefile не существует) со скобками строк “# DO NOT DELETE: Beginning of Haskell dependencies” и “# DO NOT DELETE: End of Haskell dependencies”. Если эти строки уже существуют в makefile, то старые зависимости сначала удаляются.

Не забудьте использовать те же параметры -package для команды ghc -M компиляции, которые вы использовали бы при компиляции; это позволяет генератору зависимостей находить любые импортированные модули, которые поступают из пакетов. Однако модули из пакетов не будут включены в сгенерированные зависимости (но см. параметр -include-pkg-deps ниже).

Фаза генерации зависимостей GHC может принимать дополнительные параметры, которые могут вам пригодиться. Параметры, влияющие на генерацию зависимостей:

-ddump-mod-cycles

Отобразить список циклов в графе модулей. Это полезно при попытке устранить такие циклы.

-v2

Вывести полный список зависимостей модулей в стандартный вывод. (Это стандартный флаг подробности, поэтому список также будет отображаться с -v3 и -v4; см. Параметры подробности.)

-dep-makefile ⟨file⟩

Использовать ⟨file⟩ в качестве файла make, а не makefile или Makefile. Если ⟨file⟩ не существует, mkdependHS создаёт его. Мы часто используем -dep-makefile .depend для размещения зависимостей в .depend и затем include файл .depend в Makefile.

-dep-suffix ⟨suffix⟩

Создать зависимости, объявляющие, что файлы с суффиксом .⟨suf⟩⟨osuf⟩ зависят от файлов интерфейса с суффиксом .⟨suf⟩hi, или (для {-# SOURCE #-} импортов) от .hi-boot. Допускается несколько флагов -dep-suffix Например, -dep-suffix a_ -dep-suffix b_ создаст зависимости для .hs от .hi, .a_hs от .a_hi и .b_hs от .b_hi. Если вы этого не сделаете, используется пустой суффикс.

-exclude-module=⟨file⟩

Рассматривать ⟨file⟩ как «стабильный»; то есть исключить его из зависимостей на него.

-include-pkg-deps

Рассматривать модули, импортированные из пакетов, как нестабильные, то есть генерировать зависимости от любых импортированных модулей из пакетов (включая Prelude, и все остальные стандартные библиотеки Haskell). Зависимости не отслеживаются рекурсивно в пакеты; зависимости генерируются только для модулей собственного пакета на модулях внешних пакетов, напрямую импортированных модулем собственного пакета. Этот параметр обычно используется только системами библиотек.

-include-cpp-deps

Вывести зависимости препроцессора. Это оказывает эффект только при включённом расширении языка CPP. Эти зависимости — файлы, включённые с помощью директивы препроцессора #include (а также транзитивные включения) и неявно включённые файлы, такие как стандартные заголовки препроцессора C и заголовок версии GHC. Исключение составляет временный заголовочный файл (генерируемый во время компиляции), содержащий макросы версии пакета. Поскольку это только временный файл, который GHC всегда будет генерировать, он не выводится как зависимость.

5.8.14. Модули-сироты и объявления экземпляров

Haskell определяет, что при компиляции модуля M, любое объявление экземпляра в любом модуле «ниже» M является видимым. (Модуль A находится «ниже» M если A импортируется непосредственно модулем M, или если A находится ниже модуля, который M импортирует напрямую.) В принципе, GHC должен прочитать файлы интерфейсов каждого модуля ниже M, на случай если они содержат объявление экземпляра, которое важно для M. Это было бы катастрофой на практике, поэтому GHC пытается быть умным.

В частности, если объявление экземпляра находится в том же модуле, что и определение любого типа или класса, упомянутого в заголовке объявления экземпляра (часть после «=>»; см. Правила окончания экземпляров), то GHC должен посетить этот файл интерфейса в любом случае. Пример:

module A where
  instance C a => D (T a) where ...
  data T a = ...

Объявление экземпляра актуально только в том случае, если тип T используется, и в этом случае GHC посетит файл интерфейса модуля A для поиска определения T.

Единственная проблема возникает, когда модуль содержит объявление экземпляра, и GHC не имеет другой причины для посещения модуля. Пример:

module Orphan where
  instance C a => D (T a) where ...
  class C a where ...

Здесь ни D, ни T не объявлены в модуле Orphan. Такие модули называются «модулями-сиротами». GHC идентифицирует модули-сироты и посещает файл интерфейса каждого модуля-сироты, расположенного ниже компилируемого модуля. Это обычно пустая работа, но избежать её нельзя. Поэтому вы должны по возможности минимизировать количество модулей-сирот.

Функциональные зависимости усложняют ситуацию. Предположим, у нас есть:

module B where
  instance E T Int where ...
  data T = ...

Это модуль-сирота? По-видимому, нет, потому что T объявлен в том же модуле. Но предположим, что класс E имел функциональную зависимость:

module Lib where
  class E x y | y -> x where ...

Тогда в каком-то импортирующем модуле M, ограничение (E a Int) должно быть «улучшено» путём установки a = T, несмотря на то, что в M нет явного упоминания о T.

Эти соображения приводят к следующему определению модуля-сироты:

  • Модуль-сирота содержит по крайней мере один экземпляр-сирота или по крайней мере одно правило-сирота.
  • Объявление экземпляра в модуле M является экземпляром-сиротой, если

    • Класс объявления экземпляра не объявлен в M, и
    • Либо класс не имеет функциональных зависимостей, и ни один из конструкторов типов в заголовке экземпляра не объявлен в M; либо существует функциональная зависимость, для которой ни один из конструкторов типов, упомянутых в неопределённой части заголовка экземпляра, не определён в M.

    Считается только заголовок экземпляра. В примере выше, недостаточно того, что объявление C находится в модуле A; это должно быть объявление D или T.

  • Правило переписывания в модуле M является правилом-сиротой, если ни одна из переменных, конструкторов типов или классов, которые свободны в левой части правила, не объявлены в M.

Если вы используете флаг -Worphans, GHC предупредит вас, если вы создаёте модуль-сирота. Как и любое предупреждение, вы можете отключить его с помощью -Wno-orphans, а -Werror заставит компиляцию завершиться с ошибкой, если предупреждение выдано.

Вы можете определить модуль-сироту, посмотрев в его файл интерфейса, M.hi, используя --show-iface ⟨file⟩ режим. Если на первой строке есть [orphan module], GHC считает его модулем-сиротой.

[1]

Это изменение поведения по сравнению с 6.2 и более ранними версиями.

© 2002–2007 The University Court of the University of Glasgow. All rights reserved.
Licensed under the Glasgow Haskell Compiler License.
https://downloads.haskell.org/~ghc/9.12.1/docs/users_guide/separate_compilation.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API