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

Выражения

3.15 Типы данных с именованными полями

Объявление типа данных может содержать необязательные определения имен полей (см. раздел "4.2.1" ). Эти имена полей можно использовать для создания, извлечения и обновления полей способом, который не зависит от всей структуры типа данных.

Различные типы данных не могут совместно использовать общие имена полей в одной области видимости. Имя поля можно использовать не более одного раза в конструкторе. В пределах типа данных, тем не менее, имя поля можно использовать в более чем одном конструкторе, при условии, что поле имеет один и тот же тип во всех конструкторах. Для того чтобы проиллюстрировать последнее замечание, рассмотрим:

data S = S1 { x :: Int } | S2 { x :: Int }   - OK
  data T = T1 { y :: Int } | T2 { y :: Bool }  - ПЛОХО

Здесь S является допустимым типом данных, а T - нет, потому что в последнем случае для y указан другой тип, противоречащий указанному ранее.

3.15.1 Извлечение полей

aexp -> qvar

Перевод:

выражение-аргумента -> квалифицированная-переменная

Имена полей используются в качестве селекторной функции. Когда имя поля используется в качестве переменной, оно действует как функция, которая извлекает поле из объекта. Селекторы являются связываниями верхнего уровня, и поэтому они могут быть перекрыты локальными переменными, но не могут конфликтовать с другими связываниями верхнего уровня с тем же именем. Это сокрытие затрагивает только селекторные функции, при создании записей (раздел "3.15.2" ) и их обновлении (раздел "3.15.3" ) имена полей не могут быть спутаны с обычными переменными.

Трансляция:

Имя поля f представляет собой селекторную функцию в соответствии с определением:

f x = case x of { C1 p11... p1k -> e1; ... ; Cn pn1 ... pnk -> en }

где все C1 ...Cn - конструкторы типа данных, содержащие поле с именем f, pij - это y, когда f именует собой j -ю компоненту Ci, или _ иначе, а ei - это y, когда некоторое поле в Ci имеет имя f, или undefined иначе..

3.15.2 Создание типов данных с использованием имен полей

aexp -> qcon { fbind1 , ... , fbindn } (именованная конструкция, n>=0 )
fbind -> qvar = exp

Перевод:

выражение-аргумента -> квалифицированный-конструктор { связывание-имени-поля1 , ... , связывание-имени-поляn } (именованная конструкция, n>=0 )
связывание-имени-поля -> квалифицированная-переменная = выражение

Конструктор с именованными полями может использоваться для создания значения, в котором компоненты задаются именем, а не позицией. В отличие от фигурных скобок, используемых в списках объявлений, здесь присутствие фигурных скобок не зависит от размещения текста, символы { и } должны использоваться явно. (То же самое относится к обновлению полей и образцам полей.) Создание объектов с использованием имен полей подчинено следующим ограничениям:

  • Могут использоваться только имена полей, объявленные в заданном конструкторе.
  • Имя поля не может быть использовано более одного раза.
  • Поля, которые не используются, инициализируются значением _|_.
  • Когда какое-нибудь из обязательных полей (полей, чьи типы объявлены с префиксом !) оказывается пропущенным во время создания объекта, возникает ошибка компиляции. Обязательные поля рассматриваются в разделе "4.2.1" .

Выражение F {}, где F - конструктор данных, является допустимым независимо от того, было или нет F объявлено с использованием синтаксиса записи (при условии, что F не имеет обязательных полей, см. пункт третий в приведенном выше списке); оно обозначает F _|_1 ... _|_n, где n - число аргументов F.

Трансляция:

В связывании f = v поле f именует v.

C { bs } = C (pickC1 bs undefined) ...(pickCk bs undefined)

где k - число аргументов C.

Вспомогательная функция pickCi bs d определена следующим образом:

Если i-ый компонент конструктора C имеет имя поля f и если f=v появляется в списке связываний bs, то pickCi bs d равно v. Иначе pickCi bs d равно значению по умолчанию d.

3.15.3 Обновления с использованием имен полей

aexp -> aexp<qcon> { fbind1 , ... , fbindn } (именованное обновление, n>=1 )

Перевод:

выражение-аргумента ->      выражение-аргумента<квалифицированный-конструктор> { связывание-имени-поля1 , ... , связывание-имени-поляn } (именованное обновление, n>=1 )

Значения, принадлежащие типу данных с именованными полями, можно обновлять, не боясь разрушить структуру данных. При этом создается новое значение, в котором заданные значения полей замещают те, что были в существующем значении. Обновления подчиняются следующим правилам:

  • Все имена должны быть взяты из одного типа данных.
  • По меньшей мере один конструктор должен определять все имена, упомянутые в обновлении.
  • Ни одно имя не может быть упомянуто более одного раза.
  • Если обновляемое значение не содержит все указанные имена, в ходе выполнения возникнет ошибка.

Трансляция:

Используя предыдущее определение функции pick,

e { bs }= case e of

C1 v1 ... vk1 -> C1 (pickC11 bs v1) ... (pickC 1k1 bs vk1)

Cj v1 ... vkj -> Cj (pickCj1 bs v1) ... (pickCjkj bs vkj)

-> error "Ошибка обновления"

где {C1,...,Cj} - набор конструкторов, содержащих все имена в bs, а ki - число аргументов Ci.

Вот некоторые примеры, использующие именованные поля:

data T    = C1 {f1,f2 :: Int}
          | C2 {f1 :: Int,
                f3,f4 :: Char}
Выражение Трансляция
C1 {f1 = 3} C1 3 undefined
C2 {f1 = 1, f4 = 'A', f3 = 'B'} C2 1 'B' 'A'
x {f1 = 1} case x of C1 _ f2 -> C1 1 f2
C2 _ f3 f4 -> C2 1 f3 f4

Поле f1 является общим для обоих конструкторов в T. Этот пример транслирует выражения, использующие конструкторы, записываемые с именами полей, в эквивалентные выражения, использующие те же самые конструкторы без имен полей. Если не будет единого конструктора, который определяет набор имен полей в обновлении, такого как x {f2 = 1, f3 = 'x'}, произойдет ошибка компиляции.