Опубликован: 19.09.2008 | Доступ: свободный | Студентов: 658 / 70 | Оценка: 4.50 / 5.00 | Длительность: 21:25:00
Лекция 5:

Объявления и связывания имен

Именованные поля

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

Определение конструктора в объявлении data может присвоить имена полям конструктора, используя синтаксис записи (C { ... }). Конструкторы, использующие имена полей, можно свободно смешивать с конструкторами без них. Конструктор со связанными именами полей можно по-прежнему использовать как обычный конструктор; использование имен просто является краткой записью для операций, использующих лежащий в основе позиционный конструктор. Аргументы позиционного конструктора находятся в том же порядке, что и именованные поля. Например, объявление

data C = F { f1,f2 :: Int, f3 :: Bool }

определяет тип и конструктор, идентичный тому, который соответствует определению

data C = F Int Int Bool

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

Образец F {} соответствует любому значению, построенному конструктором F, независимо от того, был объявлен F с использованием синтаксиса записи или нет.

Флажки строгости

Всякий раз, когда применяется конструктор данных, каждый аргумент конструктора вычисляется, если и только если соответствующий тип в объявлении алгебраического типа данных имеет флажок строгости, обозначаемый восклицательным знаком "!". С точки зрения лексики, "!" - обычный varsym (символ-переменной), а не reservedop (зарезервированный-оператор), он имеет специальное значение только в контексте типов аргументов объявления data.

Трансляция:

Объявление вида

data cx => T u1 ... uk = ... | K s1 ... sn | ...

где каждый si имеет вид ! ti или ti, замещает каждое вхождение K в выражении на

(\ x1 ... xn \to ( ((K op1 x1) op2 x2) ... ) opn xn)

где opi - нестрогое применение функции $, если si имеет вид ti, а opi - строгое применение функции $! (см. раздел "Предопределенные типы и классы" ), если si имеет вид ! ti. На сопоставление с образцом в K не влияют флажки строгости.

4.2.2. Объявление синонимов типов

topdecl \to type simpletype = type
simpletype \to tycon tyvar1 ... tyvark (k >= 0)

Перевод:

объявление-верхнего-уровня \to type простой-тип = тип
простой-тип \to конструктор-типа переменная-типа1 ... переменная-типаk (k >= 0)

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

type T u1 ... uk = t

и вводит конструктор нового типа - T. Тип (T t1 ... tk) эквивалентен типу t[t1/u1, ..., tk/uk]. Переменные типа u1, ... , uk должны быть различными и находятся в области видимости только над t ; если любая другая переменная типа появится в t - возникнет статическая ошибка. Конструктор нового типа T относится к виду 1 -> ... -> k ->, где виды i, к которым относятся аргументы ui, и в правой части t устанавливаются с помощью вывода вида, описанного в разделе "Объявления и связывания имен" . Например, следующее определение можно использовать для того, чтобы обеспечить альтернативный способ записи конструктора типа список:

type List = []

Символы конструкторов типов T, введенные с помощью объявлений синонимов типов, нельзя применять частично; если использовать T без полного числа аргументов - возникнет статическая ошибка.

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

type Rec a   =  [Circ a]
  data Circ a  =  Tag [Rec a]

допустимы, тогда как

type Rec a   =  [Circ a]        - неправильно
  type Circ a  =  [Rec a]         - неправильно

- нет. Аналогично, type Rec a = [Rec a] недопустим.

Синонимы типов представляют собой удобный, но строго синтаксический механизм, который делает сигнатуры более читабельными. Синоним и его определение - полностью взаимозаменяемы, за исключением типа экземпляра в объявлении instance (раздел "Объявления и связывания имен" ).

4.2.3. Переименования типов данных

topdecl \to newtype [context =>] simpletype = newconstr [deriving]
newconstr \to con atype
| con { var :: type }
simpletype \to tycon tyvar1 ... tyvark (k >= 0)

Перевод:

объявление-верхнего-уровня \to newtype [контекст =>] простой-тип = новая-конструкция [deriving-инструкция]
новая-конструкция \to конструктор a-тип
| конструктор { переменная :: тип }
простой-тип \to конструтор-типа переменная-типа1 ... переменная-типаk (k >= 0)

Объявление вида

newtype cx => T u1 ... uk = N t

вводит новый тип, который имеет то же самое представление, что и существующий тип. Тип (T u1 ... uk) переименовывает тип данных t. Он отличается от синонима типа тем, что создает отдельный тип, который должен явно приводиться к исходному типу или обратно. Также, в отличие от синонимов типа, newtype может использоваться для определения рекурсивных типов. Конструктор N в выражении приводит значение типа t к типу (T u1 ... uk). Использование N в образце приводит значение типа (T u1 ... uk) к типу t. Эти приведения типов могут быть реализованы без накладных расходов времени выполнения, newtype не меняет лежащее в основе представление объекта.

Новые экземпляры (см. раздел "Объявления и связывания имен" ) можно определить для типа, определенного с помощью newtype, но нельзя определить для синонима типа. Тип, созданный с помощью newtype, отличается от алгебраического типа данных тем, что представление алгебраического типа данных имеет дополнительный уровень непрямого доступа. Это отличие может сделать доступ к представлению менее эффективным. Различие отражено в различных правилах для сопоставления с образцом (см. раздел "Выражения" ). В отличие от алгебраического типа данных, конструктор нового типа данных N является неповышенным, так что N \perp - то же самое, что и \perp.

Следующие примеры разъясняют различия между data (алгебраическими типами данных), type (синонимами типов) и newtype (переименованными типами). При данных объявлениях

data D1 = D1 Int
  data D2 = D2 !Int
  type S = Int
  newtype N = N Int
  d1 (D1 i) = 42
  d2 (D2 i) = 42
  s i = 42
  n (N i) = 42

выражения ( d1 \perp ), ( d2 \perp ) и (d2 (D2 \perp ) ) эквивалентны \perp, поскольку ( n \perp ), ( n ( N  \perp ) ), ( d1 ( D1 \perp ) ) и ( s \perp ) эквивалентны 42. В частности, ( N \perp ) эквивалентно \perp, тогда как ( D1 \perp ) неэквивалентно \perp.

Необязательная часть deriving объявления newtype трактуется так же, как и компонента deriving объявления data (см. раздел "Объявления и связывания имен" ).

В объявлении newtype можно использовать синтаксис для именования полей, хотя, конечно, может быть только одно поле. Таким образом,

newtype Age = Age { unAge :: Int }

вводит в область видимости и конструктор, и деструктор:

Age   :: Int -> Age
  unAge :: Age -> Int

4.3 Классы типов и перегрузка

4.3.1 Объявления классов

topdecl \to class [scontext =>] tycls tyvar [where cdecls]
scontext \to simpleclass
| ( simpleclass1 , ... , simpleclassn ) (k >= 0)
simpleclass \to qtycls tyvar
cdecls \to { cdecl1 ; ... ; cdecln } (k >= 0)
cdecl \to gendecl
| (funlhs | var) rhs

Перевод:

объявление-верхнего-уровня \to class [простой-контекст \Rightarrow ] класс-типа переменная-типа [ where список-объявлений-классов]
простой-контекст \to простой-класс
| ( простой-класс1 , ... , простой-классn ) (k >= 0)
простой-класс \to квалифицированный-класс-типа переменная-типа
список-объявлений-классов \to { объявление-класса1 ; ... ; объявление-классаn } (k >= 0)
объявление-класса \to общее-объявление
| (левая-часть-функции | переменная) правая-часть

Объявление класса вводит новый класс и операции (методы класса) над ним. Объявление класса имеет общий вид:

class cx => C u where cdecls

Это объявление вводит новый класс C ; переменная типа u находится только в области видимости над сигнатурами методов класса в теле класса. Контекст cx определяет суперклассы C, как описано ниже; единственной переменной типа, на которую можно ссылаться в cx, является u.

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

Часть cdecls (список-объявлений-классов) в объявлении class содержит три вида объявлений:

  • Объявление класса вводит новые методы класса vi, чья область видимости простирается за пределы объявления class. Методы класса в объявлении класса являются в точности vi, для которых есть явная сигнатура типа
    vi :: cxi => ti

    в cdecls. Методы класса совместно со связанными переменными и именами полей используют пространство имен верхнего уровня; они не должны конфликтовать с другими именами в области видимости. То есть метод класса не может иметь то же имя, что и определение верхнего уровня, имя поля или другой метод класса.

    Типом метода класса верхнего уровня vi является:

    vi:: forall u, w. (C u, cxi) =>ti

    ti должен ссылаться на u ; оно может ссылаться на переменные типов w, отличные от u, в этом случае тип vi является полиморфным в u и w. cxi может ограничивать только w, в частности, cxi не может ограничивать u. Например,

    class Foo a where
        op :: Num b => a -> b -> a

    Здесь типом op является forall a, b. (Foo a, Num b) => a -> b -> a.

  • cdecls может также содержать infix -объявления для любого из методов класса (но не для других значений). Тем не менее, так как методы класса объявлены значениями верхнего уровня, infix-объявления для методов класса могут, в качестве альтернативы, появляться на верхнем уровне, вне объявления класса.
  • Наконец, cdecls может содержать методы класса по умолчанию для любого из vi. Метод класса по умолчанию для vi используется, если для него не задано связывание в конкретном объявлении instance (см. раздел "Объявления и связывания имен" ). Объявление метода по умолчанию является обычным определением значения, за исключением того, что левая часть может быть только переменной или определением функции. Например, объявление
    class Foo a where
        op1, op2 :: a -> a
        (op1, op2) = ...

    недопустимо, потому что левой частью объявления метода по умолчанию является образец.

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

Объявление class без части where может оказаться полезным для объединения совокупности классов в больший класс, который унаследует все методы исходных классов. Например,

class  (Read a, Show a) => Textual a

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