Модули
Модуль определяет совокупность значений, типов данных, синонимов типов, классов и т.д. (см. лекцию "4" ) в окружении, созданном набором списков импорта (введенных в область видимости ресурсов других модулей). Он экспортирует некоторые из этих ресурсов, делая их доступными другим модулям. Мы используем термин сущность для ссылки на значение, тип или класс, определенный, импортированный или, возможно, экспортированный из модуля.
Программа на Haskell - это совокупность модулей, один из которых условно должен называться Main и должен экспортировать значение main. Значением программы является значение идентификатора main в модуле Main, которое должно иметь тип IO t для некоторого типа t (см. лекцию "7" ). Когда выполняется программа, вычисляется значение main и результат (типа t ) отбрасывается.
Модули могут ссылаться на другие модули посредством явных объявлений import, каждое из которых задает имя импортируемого модуля и его сущности, которые будут импортированы. Модули могут быть взаимно рекурсивны.
Модули используются для управления пространством имен и не являются главными значениями класса. Многомодульная программа на Haskell может быть преобразована в программу с одним модулем, если дать каждой сущности уникальное имя, соответственно заменить все вхождения, ссылающиеся на эти имена, и затем объединить все тела модулей. (Есть два незначительных исключения из этого утверждения. Первое - объявления default видны в области видимости одного модуля (раздел "4.3.4" ). Второе - Правило 2 ограничения мономорфизма (раздел "4.5.5" ) влияет на границы модулей. ) Например, рассмотрим программу с тремя модулями:
module Main where import A import B main = A.f >> B.f module A where f = ... module B where f = ...
Она эквивалентна следующей программе с одним модулем:
module Main where main = af >> bf af = ... bf = ...
Поскольку модули могут быть взаимно рекурсивными, с помощью модулей можно свободно разделить программу на части, не обращая внимания на зависимости.
Пространство имен для самих модулей является плоским, оно связывает каждый модуль с уникальным именем модуля (которые являются идентификаторами Haskell , начинающимися с заглавной буквы, т.е. modid ). Есть один, отличный от остальных, модуль Prelude, который импортируется во все модули по умолчанию (см. раздел "5.6" ), плюс набор модулей стандартной библиотеки, которые можно импортировать по требованию.
5.1. Структура модуля
Модуль определяет взаимно рекурсивную область видимости, содержащую объявления для связывания значений, типов данных, синонимов типов, классов и т.д. (см. лекцию "4" ).
module | module modid [exports] where body | ||
| | body | ||
body | { impdecls ; topdecls } | ||
| | { impdecls } | ||
| | { topdecls } | ||
modid | conid | ||
impdecls | impdecl1 ; ... ; impdecln | (n <- 1) | |
topdecls | topdecl1 ; ... ; topdecln | (n <- 1) |
Переводы:
модуль | module идентификатор-модуля [список-экспорта] where тело | ||
| | тело | ||
тело | { список-объявлений-импорта ; список-объявлений-верхнего-уровня } | ||
| | { список-объявлений-импорта } | ||
| | { список-объявлений-верхнего-уровня } | ||
идентификатор-модуля | идентификатор-конструктора | ||
список-объявлений-импорта | объявление-импорта1 ; ... ; объявление-импортаn | (n <- 1) | |
список-объявлений-верхнего-уровня | объявление-верхнего-уровня1 ; ... ; объявление-верхнего-уровняn | (n <- 1) |
Модуль начинается с заголовка - ключевого слова module, имени модуля и списка экспортируемых сущностей (заключенного в круглые скобки). За заголовком следует возможно пустой список объявлений import (impdecls, раздел "5.3" ), который задает импортируемые модули, необязательно ограничивая импортируемые связывания имен. За ним следует возможно пустой список объявлений верхнего уровня (topdecls, лекция "4" ).
Разрешена сокращенная форма модуля, состоящая только из тела модуля. Если используется сокращенная форма, то предполагается заголовок 'module Main(main) where'. Если первая лексема в сокращенном модуле не является {, то для верхнего уровня модуля применяется правило размещения.
5.2. Списки экспорта
exports | ( export1 , ... , exportn [ , ] ) | (n <- 0) | |
export | qvar | ||
| | qtycon [(..) | ( cname1 , ... , cnamen )] | (n <- 0) | |
| | qtycls [(..) | ( var1 , ... , varn )] | (n <- 0) | |
| | module modid | ||
cname | var | con |
Переводы:
список-экспорта | ( экспорт1 , ... , экспортn [ , ] ) | (n <- 0) | |
экспорт | квалифицированная-переменная | ||
| | квалифицированный-конструктор-типа [(..) | ( c-имя1 , ... , c-имяn )] | (n <- 0) | |
| | квалифицированный-класс-типа [(..) | ( квалифицированная-переменная1 , ... , квалифицированная-переменнаяn )] | (n <- 0) | |
| | module идентификатор-модуля | ||
c-имя | переменная | конструктор |
Список экспорта определяет сущности, которые экспортируются посредством объявления модуля. Реализация модуля может экспортировать только ту сущность, которую он объявляет или которую он импортирует из некоторого другого модуля. Если список экспорта пропущен, все значения, типы и классы, определенные в модуле, экспортируются, кроме тех, что были импортированы.
Сущности в списке экспорта можно перечислить следующим образом:
- Значение, имя поля или метод класса, объявленные в теле модуля или импортированные, можно указать, задав имя значения в качестве qvarid, которое должно находиться в области видимости. Операторы должны быть заключены в круглые скобки, чтобы превратить их в qvarid.
- Алгебраический тип данных T, объявленный посредством объявления data или newtype, можно указать одним из трех способов:
- Форма T указывает тип, но не конструкторы или имена полей. Способность экспортировать тип без его конструкторов позволяет конструировать абстрактные типы данных (см. раздел "5.8" ).
- Форма T ( c1 ,..., cn ) указывает тип и некоторые или все его конструкторы и имена полей.
- Сокращенная форма T(..) указывает тип и все его конструкторы и имена полей, которые в настоящее время находятся в области видимости (квалифицированные или нет).
Во всех случаях (возможно квалифицированный) конструктор типа T должен находиться в области видимости. Конструктор и имена полей ci во второй форме являются неквалифицированными; одно из этих подчиненных имен является правильным, если и только если (a) оно именует собой конструктор или поле T и (b) конструктор или поле находится в области видимости в теле модуля, при этом неважно, находится он в области видимости под квалифицированным или неквалифицированном именем. Например, следующее объявление является правильным:
module A( Mb.Maybe( Nothing, Just ) ) where import qualified Maybe as Mb
Конструкторы данных нельзя указывать в списках экспорта, кроме как с помощью подчиненных имен, потому что иначе они не могут быть отличимы от конструкторов типов.
- Синоним типа T, объявленный в объявлении type, можно указать с помощью формы T, где T находится в области видимости.
- Класс C с операциями f1, ..., fn, объявленный в объявлении class, можно указать одним из трех способов:
- Форма C указывает класс, но не методы класса.
- Форма C ( f1 ,..., fn ) , указывает класс и некоторых или все методы.
- Сокращенная форма C (..) указывает класс и все его методы, которые находятся в области видимости (квалифицированные или нет).
Во всех случаях C должен находиться в области видимости. Во второй форме одно из (неквалифицированных) подчиненных имен fi является правильным, если и только если (a) оно именует собой метод класса C и (b) метод класса находится в области видимости в теле модуля, неважно, находится он в области видимости под квалифицированным или неквалифицированным именем.
- Форма "module M" указывает набор всех сущностей, которые находятся в области видимости с неквалифицированным именем "e" и квалифицированным именем "M.e". Этот набор может быть пуст. Например:
module Queue( module Stack, enqueue, dequeue ) where import Stack ...
Здесь модуль Queue использует имя модуля Stack в своем списке экспорта, чтобы сократить имена всех сущностей, импортированных из Stack.
Модуль может указать свои собственные локальные определения в своем списке экспорта, используя свое собственное имя в синтаксисе "module M", потому что локальное объявление вводит в область видимости и квалифицированное, и неквалифицированное имя (раздел "5.5.1" ). Например:
module Mod1( module Mod1, module Mod2 ) where import Mod2 import Mod3
Здесь модуль Mod1 экспортирует все локальные определения, а также импортированные из Mod2, но не импортированные из Mod3.
Будет ошибкой использовать module M в списке экспорта, если M не является модулем, обладающим списком экспорта, или M не импортирован по меньшей мере посредством одного объявления импорта (квалифицированным или неквалифицированным).
Списки экспорта являются общими: набор сущностей, экспортируемых посредством списка экспорта является объединением сущностей, экспортируемых отдельными элементами списка.
Нет никакого различия для импортируемого модуля, как сущность была экспортирована. Например, имя поля f из типа данных T можно экспортировать отдельно ( f, пункт (1) выше) или как явно указанный член его типа данных ( T(f) , пункт (2)), или как неявно указанный член ( T(..), пункт (2)), или посредством экспорта всего модуля ( module M, пункт (5)).
Неквалифицированные имена сущностей, экспортируемые модулем, должны отличаться друг от друга (в пределах их соответствующего пространства имен). Например,
module A ( C.f, C.g, g, module B ) where - неправильный модуль import B(f) import qualified C(f,g) g = f True
Непосредственно в пределах модуля A конфликтов имен нет, но есть конфликт имен в списке экспорта между C.g и g (предположим, что C.g и g - различные сущности, вспомните, что модули могут импортировать друг друга рекурсивно) и между module B и C.f (предположим, что B.f и C.f - различные сущности).