Модуль определяет совокупность значений, типов данных, синонимов типов, классов и т.д. (см. главу 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), плюс набор модулей стандартной библиотеки, которые можно импортировать по требованию (см. часть II).
Модуль определяет взаимно рекурсивную область видимости, содержащую объявления для связывания значений, типов данных, синонимов типов, классов и т.д. (см. главу 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'. Если первая лексема в сокращенном модуле не является {, то для верхнего уровня модуля применяется правило размещения.
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-имя | -> | переменная | конструктор |
Список экспорта определяет сущности, которые экспортируются посредством объявления модуля. Реализация модуля может экспортировать только ту сущность, которую он объявляет или которую он импортирует из некоторого другого модуля. Если список экспорта пропущен, все значения, типы и классы, определенные в модуле, экспортируются, кроме тех, что были импортированы.
Сущности в списке экспорта можно перечислить следующим образом:
Модуль может указать свои собственные локальные определения в своем списке экспорта, используя свое собственное имя в синтаксисе "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 --- различные сущности).
impdecl | -> | import [qualified] modid [as modid] [impspec] | |
| | (пустое объявление) | ||
impspec | -> | ( import1 , ... , importn [ , ] ) | (n>=0) |
| | hiding ( import1 , ... , importn [ , ] ) | (n>=0) | |
import | -> | var | |
| | tycon [ (..) | ( cname1 , ... , cnamen )] | (n>=0) | |
| | tycls [(..) | ( var1 , ... , varn )] | (n>=0) | |
cname | -> | var | con |
Перевод: | |||
объявление-импорта | -> | import [qualified] идентификатор-модуля [as идентификатор-модуля] [спецификатор-импорта] | |
| | (пустое объявление) | ||
спецификатор-импорта | -> | ( импорт1 , ... , импортn [ , ] ) | (n>=0) |
| | hiding ( импорт1 , ... , импортn [ , ] ) | (n>=0) | |
импорт | -> | переменная | |
| | конструктор-типа [ (..) | ( c-имя1 , ... , c-имяn )] | (n>=0) | |
| | класс-типа [(..) | ( переменная1 , ... , переменнаяn )] | (n>=0) | |
c-имя | -> | переменная | конструктор |
Сущности, экспортируемые модулем, можно ввести в область видимости другого модуля посредством объявления import в начале модуля. В объявлении import указывается импортируемый модуль и необязательно задаются импортируемые сущности. Один модуль можно импортировать с помощью более чем одного объявления import. Импортированные имена служат в качестве объявлений верхнего уровня: их область видимости простирается над всем телом модуля, но может быть сокрыта локальными связываниями имен отличного от верхнего уровня.
Влияние многократных объявлений import строго кумулятивно: сущность находится в области видимости, если она импортирована посредством любого из объявлений import в модуле. Порядок объявлений импорта не существенен.
С точки зрения лексики, каждый из терминальных символов "as", "qualified" и "hiding" является varid (идентификатором-переменной), а не reservedid (зарезервированным-идентификатором). Они имеют специальное значение только в контексте объявления import; их также можно использовать в качестве переменных.
Какие точно сущности должны быть импортированы, можно задать одним из следующих трех способов:
В списке должны быть указаны только сущности, экспортированные импортируемым модулем. Список может быть пуст, в этом случае ничто, кроме экземпляров, не будет импортировано.
Будет ошибкой указать в списке hiding сущность, которая на самом деле не экспортируется импортируемым модулем.
Для каждой сущности, импортируемой в соответствии с правилами раздела 5.3.1, расширяется окружение верхнего уровня. Если объявление импорта использует ключевое слово qualified, то только квалифицированное имя сущности вводится в область видимости. Если ключевое слово qualified опущено, то оба имени: квалифицированное и неквалифицированное имя сущности --- вводятся в область видимости. В разделе 5.5.1 квалифицированные имена описаны более подробно.
Квалификатор импортированного имени является именем импортированного модуля или локальным синонимом, заданным с помощью инструкции as (раздел 5.3.3) в инструкции import. Следовательно, квалификатор необязательно является именем модуля, в котором первоначально была объявлена сущность.
Возможность исключить неквалифицированные имена позволяет программисту осуществлять полное управление
пространством неквалифицированных имен: локально определенная сущность может совместно использовать то же
имя, что и импортируемая сущность с квалифицированным именем:
module Ring where
import qualified Prelude -- Все имена из Prelude должны быть квалифицированными
import List( nub )
l1 + l2 = l1 Prelude.++ l2 -- Этот + отличается от + в Prelude
l1 * l2 = nub (l1 + l2) -- Эта * отличается от * в Prelude
succ = (Prelude.+ 1)
Импортированным модулям можно присвоить локальный синоним в модуле, который осуществляет импортирование, для этого используется
инструкция as.
Например, в
import qualified VeryLongModuleName as C
к импортированным сущностям можно обращаться, используя в качестве квалификатора `C.' вместо
`VeryLongModuleName.'. Это также позволяет другому модулю быть замененным
на VeryLongModuleName без изменения квалификаторов, используемых для импортированного модуля.
Более чем один модуль в области видимости может
использовать тот же самый квалификатор, при условии, что все имена по-прежнему могут быть однозначно разрешены.
Например:
module M where
import qualified Foo as A
import qualified Baz as A
x = A.f
Этот модуль является правильным только при условии, что и Foo, и Baz не экспортируют f.
Инструкцию as можно также использовать в инструкции import без qualified:
import Foo as A(f)
Это объявление вводит в область видимости f и A.f.
Для того чтобы разъяснить вышеупомянутые правила импорта, предположим, что модуль A экспортирует x и y. Тогда эта таблица показывает, какие имена будут введены в область видимости с помощью заданного объявления импорта:
Объявление импорта | Имена, введенные в область видимости |
import A | x, y, A.x, A.y |
import A() | (ничего) |
import A(x) | x, A.x |
import qualified A | A.x, A.y |
import qualified A() | (ничего) |
import qualified A(x) | A.x |
import A hiding () | x, y, A.x, A.y |
import A hiding (x) | y, A.y |
import qualified A hiding () | A.x, A.y |
import qualified A hiding (x) | A.y |
import A as B | x, y, B.x, B.y |
import A as B(x) | x, B.x |
import qualified A as B | B.x, B.y |
Объявления экземпляров нельзя явно указать в списках импорта или экспорта. Все экземпляры в области видимости модуля всегда экспортируются, и любое объявление импорта вводит в область видимости все экземпляры импортируемого модуля. Таким образом, объявление экземпляра находится в области видимости, если и только если цепочка объявлений import ведет к модулю, содержащему объявление экземпляра.
Например, import M() не вводит
никакие новые имена из модуля M в область видимости, но вводит все экземпляры,
которые видны в M. Модуль, чья единственная цель состоит в том, чтобы обеспечить объявления
экземпляров, может иметь пустой список экспорта. Например,
module MyInstances() where
instance Show (a -> b) where
show fn = "<<function>>"
instance Show (IO a) where
show io = "<<IO action>>"
Квалифицированное имя имеет вид modid.name (идентификатор-модуля.имя) (раздел 2.4). Квалифицированное имя вводится в область видимости:
Если модуль содержит связанное вхождение имени, например, f или A.f, должна быть возможность однозначно решить, на какую сущность при этом ссылаются; то есть должно быть только одно связывание для f или A.f соответственно.
Ошибки не будет, если существуют имена, которые нельзя так разрешить, при условии, что программа не содержит ссылок на эти имена. Например:
module A where
import B
import C
tup = (b, c, d, x)
module B( d, b, x, y ) where
import D
x = ...
y = ...
b = ...
module C( d, c, x, y ) where
import D
x = ...
y = ...
c = ...
module D( d ) where
d = ...
Рассмотрим определение tup.
Имя, встречающееся в сигнатуре типа или infix-объявлениях,
всегда является неквалифицированным и однозначно ссылается на другое объявление в
том же списке объявлений (за исключением того, что infix-объявление для
метода класса может встречаться на верхнем уровне, см. раздел 4.4.2). Например,
следующий модуль является правильным:
module F where
sin :: Float -> Float
sin x = (x::Float)
f x = Prelude.sin (F.sin x)
Локальное объявление sin
является правильным, даже если sin из Prelude неявно находится в области видимости. Ссылки на Prelude.sin и F.sin должны быть обе квалифицированными для того, чтобы
однозначно определить, какой подразумевается sin. Тем не менее, неквалифицированное
имя sin в сигнатуре типа в первой строке F однозначно
ссылается на локальное объявление sin.
Каждый модуль в программе на Haskell должен быть замкнутым. То есть
каждое имя, явно указанное в исходном тексте,
должно быть локально определено или импортировано из другого модуля.
Тем не менее, нет необходимости в том, чтобы сущности, которые требуются компилятору для контроля типов или другого
анализа времени компиляции, были импортированы, если к ним нет обращений по имени. Система компиляции Haskell несет ответственность за нахождение
любой информации, необходимой для компиляции без помощи
программиста. То есть импорт переменной x не требует,
чтобы типы данных и классы в сигнатуре x были
введены в модуль наряду с x, если к этим сущностям не
обращаются по имени в пользовательской программе. Система Haskell
молча импортирует любую информацию, которая должна сопровождать
сущность для контроля типов или любых других целей. Такие сущности не требуется
даже явно экспортировать: следующая программа является правильной, хотя
T не избегает M1:
module M1(x) where
data T = T
x = T
module M2 where
import M1(x)
y = x
В этом примере нет способа указать явную сигнатуру типа
для y, т.к. T не находится в области видимости.
Независимо от того, экспортируется T явно или нет, модуль M2 знает
достаточно о T, чтобы правильно выполнить контроль соответствия типов программы.
На тип экспортируемой сущности не влияет неэкспортируемые синонимы типов.
Например, в
module M(x) where
type T = Int
x :: T
x = 1
типом x является и T, и Int; они взаимозаменяемы, даже
когда T не находится в области видимости. То есть определение T доступно
любому модулю, который сталкивается с ним, независимо от того, находится имя T
в области видимости или нет. Единственная причина экспортировать T состоит в том, чтобы позволить другим модулям
обращаться к нему по имени; контроль типов находит определение T, если оно
необходимо, независимо от того, было оно экспортировано или нет.
Prelude и модули библиотеки отличаются от других модулей тем, что их семантика (но не их реализация) является неизменной частью определения языка Haskell . Это означает, например, что компилятор может оптимизировать вызовы функций Haskell , не принимая во внимание исходный текст Prelude.
Модуль Prelude автоматически импортируется во все модули, как если бы была инструкция `import Prelude', если и только если он не импортируется посредством явного объявления import. Это условие для явного импорта позволяет выборочно импортировать сущности, определенные в Prelude, точно так же, как сущности из любого другого модуля.
Семантика сущностей в Prelude задана ссылочной реализацией Prelude, написанной на Haskell , данной в главе 8. Некоторые типы данных (например, Int) и функции (например, сложение Int) нельзя задать непосредственно на Haskell . Так как обработка таких сущностей зависит от реализации, они формально не описаны в главе 8. Реализация Prelude также является неполной при обработке кортежей: должно быть бесконечное семейство кортежей и объявлений их экземпляров, но реализация лишь задает схему.
В главе 8 дано определение модуля Prelude с использованием нескольких других модулей: PreludeList, PreludeIO и так далее. Эти модули не являются частью Haskell 98, и их нельзя импортировать отдельно. Они просто помогают объяснить структуру модуля Prelude; их следует рассматривать как часть ее реализации, а не часть определения языка.
Правила о Prelude были разработаны так, чтобы имелась
возможность использовать имена из Prelude для нестандартных целей; тем не менее,
каждый модуль, который так делает, должен иметь объявление import,
которое делает это нестандартное использование явным. Например:
module A( null, nonNull ) where
import Prelude hiding( null )
null, nonNull :: Int -> Bool
null x = x == 0
nonNull x = not (null x)
Модуль A переопределяет null и содержит неквалифицированную ссылку на null
в правой части nonNull. Последнее было бы неоднозначно
без наличия инструкции hiding(null) в объявлении import Prelude. Каждый
модуль, который импортирует неквалифицированное имя A и затем создает неквалифицированную
ссылку на null, должен также разрешить неоднозначное использование null так же, как это
делает A. Таким образом, есть небольшая опасность случайно скрыть имена из Prelude.
Имеется возможность создать и использовать другой модуль, который будет служить вместо Prelude. За исключением того факта, что модуль Prelude неявно
импортируется в любой модуль, Prelude является обычным модулем Haskell ; он является
особенным
только в том, что обращение к некоторым сущностям Prelude происходит посредством специальных
синтаксических конструкций. Переопределение имен, используемых Prelude, не влияет
на значение этих специальных конструкций. Например, в
module B where
import Prelude()
import MyPrelude
f x = (x,x)
g x = (,) x x
h x = [x] ++ []
явное объявление import Prelude() предотвращает автоматический
импорт Prelude, в то время как объявление import MyPrelude вводит
в область видимости нестандартное начало (prelude).
Специальный синтаксис для кортежей (например, (x,x) и (,)) и списков
(например, [x] и []) продолжает обращаться к кортежам и спискам,
определенным стандартным Prelude;
не существует способа переопределить значение [x], например, в терминах
другой реализации списков.
С другой стороны, использование ++ не является специальным синтаксисом, поэтому он обращается
к ++, импортированному из MyPrelude.
Невозможно, тем не менее, скрыть объявления instance в Prelude. Например, нельзя определить новый экземпляр для Show Char.
Способность экспортировать тип данных без его конструкторов
позволяет конструировать абстрактные типы данных (ADT). Например,
ADT для стеков можно определить так:
module Stack( StkType, push, pop, empty ) where
data StkType a = EmptyStk | Stk a (StkType a)
push x s = Stk x s
pop (Stk _ s) = s
empty = EmptyStk
Модули, импортирующие Stack, не могут создавать значения типа StkType,
потому что они не имеют доступа к конструкторам типа.
Вместо этого они должны использовать push, pop и empty, чтобы создать такие значения.
Также имеется возможность строить ADT на верхнем уровне существующего типа
посредством использования объявления newtype. Например, стеки можно определить
через списки:
module Stack( StkType, push, pop, empty ) where
newtype StkType a = Stk [a]
push x (Stk s) = Stk (x:s)
pop (Stk (_:s)) = Stk s
empty = Stk []