Описание Haskell 98: Модули Описание Haskell 98
наверх | назад | вперед | содержание | предметный указатель функций

5  Модули

Модуль определяет совокупность значений, типов данных, синонимов типов, классов и т.д. (см. главу 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).

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-имя -> переменная | конструктор

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

Сущности в списке экспорта можно перечислить следующим образом:

  1. Значение, имя поля или метод класса, объявленные в теле модуля или импортированные, можно указать, задав имя значения в качестве qvarid, которое должно находиться в области видимости. Операторы должны быть заключены в круглые скобки, чтобы превратить их в qvarid.

  2. Алгебраический тип данных 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

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

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

  4. Класс C с операциями f1, ..., fn , объявленный в объявлении class, можно указать одним из трех способов:
    • Форма C указывает класс, но не методы класса.
    • Форма C ( f1 ,..., fn ), указывает класс и некоторых или все методы.
    • Сокращенная форма C (..) указывает класс и все его методы, которые находятся в области видимости (квалифицированные или нет).
    Во всех случаях C должен находиться в области видимости. Во второй форме одно из (неквалифицированных) подчиненных имен fi является правильным, если и только если (a) оно именует собой метод класса C и (b) метод класса находится в области видимости в теле модуля, неважно, находится он в области видимости под квалифицированным или неквалифицированным именем.

  5. Форма "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 --- различные сущности).

5.3  Объявления импорта

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; их также можно использовать в качестве переменных.

5.3.1  Что такое импортирование

Какие точно сущности должны быть импортированы, можно задать одним из следующих трех способов:

  1. Импортируемые сущности можно задать явно, перечислив их в круглых скобках. Элементы списка имеют ту же форму, что элементы в списках экспорта, за исключением того, что нельзя использовать квалификаторы и нельзя использовать сущность `module modid'. Когда форма (..) импорта используется для типа или класса, (..) ссылается на все конструкторы, методы или имена полей, экспортированные из модуля.

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

  2. Сущности могут быть исключены посредством использования формы hiding(import1 , ... , importn ), которая указывает, что все объекты, экспортированные названным модулем, должны быть импортированы, за исключением указанных в списке. Конструкторы данных можно указать непосредственно в списках hiding без использования в префиксе связанного с ним типа. Таким образом, в

      import M hiding (C)

    любой конструктор, класс, или тип, названный C, исключен. Напротив, используя C в списке импорта, вы укажете лишь класс или тип.

    Будет ошибкой указать в списке hiding сущность, которая на самом деле не экспортируется импортируемым модулем.

  3. Наконец, если impspec пропущен, то все сущности, экспортируемые указанным модулем, будут импортированы.

5.3.2  Импортирование с использованием квалификаторов

Для каждой сущности, импортируемой в соответствии с правилами раздела 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)

5.3.3  Локальные синонимы

Импортированным модулям можно присвоить локальный синоним в модуле, который осуществляет импортирование, для этого используется инструкция 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.

5.3.4  Примеры

Для того чтобы разъяснить вышеупомянутые правила импорта, предположим, что модуль 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
Во всех случаях все объявления экземпляров в области видимости в модуле A будут импортированы (раздел 5.4).

5.4  Импортирование и экспортирование объявлений экземпляров

Объявления экземпляров нельзя явно указать в списках импорта или экспорта. Все экземпляры в области видимости модуля всегда экспортируются, и любое объявление импорта вводит в область видимости все экземпляры импортируемого модуля. Таким образом, объявление экземпляра находится в области видимости, если и только если цепочка объявлений 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>>"

5.5  Конфликт имен и замыкание

5.5.1  Квалифицированные имена

Квалифицированное имя имеет вид modid.name (идентификатор-модуля.имя) (раздел 2.4). Квалифицированное имя вводится в область видимости:

5.5.2  Конфликты имен

Если модуль содержит связанное вхождение имени, например, 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.

5.5.3  Замыкание

Каждый модуль в программе на 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, если оно необходимо, независимо от того, было оно экспортировано или нет.

5.6  Стандартное начало (Prelude)

Многие возможности Haskell определены в самом Haskell в виде библиотеки стандартных типов данных, классов и функций, называемой "стандартным началом (prelude)." В Haskell стандартное начало содержится в модуле Prelude. Есть также много предопределенных модулей библиотеки, которые обеспечивают менее часто используемые функции и типы. Например, комплексные числа, массивы, и большинство операций ввода - вывода являются частью стандартной библиотеки. Они описаны в части II. Отделение библиотеки от Prelude имеет преимущество в виде сокращения размера и сложности Prelude, позволяя ему более легко становиться частью программы и расширяя пространство полезных имен, доступных программисту.

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

5.6.1  Модуль Prelude

Модуль Prelude автоматически импортируется во все модули, как если бы была инструкция `import Prelude', если и только если он не импортируется посредством явного объявления import. Это условие для явного импорта позволяет выборочно импортировать сущности, определенные в Prelude, точно так же, как сущности из любого другого модуля.

Семантика сущностей в Prelude задана ссылочной реализацией Prelude, написанной на Haskell , данной в главе 8. Некоторые типы данных (например, Int) и функции (например, сложение Int) нельзя задать непосредственно на Haskell . Так как обработка таких сущностей зависит от реализации, они формально не описаны в главе 8. Реализация Prelude также является неполной при обработке кортежей: должно быть бесконечное семейство кортежей и объявлений их экземпляров, но реализация лишь задает схему.

В главе 8 дано определение модуля Prelude с использованием нескольких других модулей: PreludeList, PreludeIO и так далее. Эти модули не являются частью Haskell 98, и их нельзя импортировать отдельно. Они просто помогают объяснить структуру модуля Prelude; их следует рассматривать как часть ее реализации, а не часть определения языка.

5.6.2  Сокрытие имен из 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.

5.7  Раздельная компиляция

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

5.8  Абстрактные типы данных

Способность экспортировать тип данных без его конструкторов позволяет конструировать абстрактные типы данных (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 []


Описание Haskell 98
наверх | назад | вперед | содержание | предметный указатель функций
Декабрь 2002