Россия, Петерубрг, СПБ-ГПУ, 1998 |
Выражения
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 Извлечение полей
Перевод:
Имена полей используются в качестве селекторной функции. Когда имя поля используется в качестве переменной, оно действует как функция, которая извлекает поле из объекта. Селекторы являются связываниями верхнего уровня, и поэтому они могут быть перекрыты локальными переменными, но не могут конфликтовать с другими связываниями верхнего уровня с тем же именем. Это сокрытие затрагивает только селекторные функции, при создании записей (раздел "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 Создание типов данных с использованием имен полей
Перевод:
выражение-аргумента | -> | квалифицированный-конструктор { связывание-имени-поля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 Обновления с использованием имен полей
Перевод:
выражение-аргумента | -> | выражение-аргумента<квалифицированный-конструктор> { связывание-имени-поля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'}, произойдет ошибка компиляции.