Россия, Петерубрг, СПБ-ГПУ, 1998 |
Объявления и связывания имен
4.1.4 Семантика типов и классов
В этом разделе мы дадим неформальные детали системы типов. (Уодлер (Wadler) и Блотт (Blott) [12] и Джонс (Jones) [7] рассматривают соответственно классы типов и конструируемые классы более подробно.)
Система типов в Haskell приписывает тип каждому выражению в программе. Вообще, тип имеет вид forall u. cx => t, где u - набор переменных типов u1, ..., un. В любом таком типе любая из стоящих под квантором всеобщности переменных типов ui, которая является свободной в cx, должна также быть свободной в t. Кроме того, контекст cx должен иметь вид, описанный выше в разделе "Объявления и связывания имен" . В качестве примера приведем некоторые допустимые типы:
Eq a => a -> a (Eq a, Show a, Eq b) => [a] -> [b] -> String (Eq (f a), Functor f) => (a -> b) -> f a -> f b -> Bool
В третьем типе ограничение Eq (f a) не может быть упрощено, потому что f стоит под квантором всеобщности.
Тип выражения e зависит от окружения типа, которое задает типы для свободных переменных в e, и окружения класса, которое объявляет, какие типы являются экземплярами каких классов (тип становится экземпляром класса только посредством наличия объявления instance или инструкции deriving ).
Типы связаны прямым порядком обобщения (указан ниже); наиболее общий тип, с точностью до эквивалентности, полученный посредством обобщения, который можно присвоить конкретному выражению (в заданном окружении), называется его основным типом. В системе типов в языке Haskell , которая является расширением системы Хиндли-Милнера (Hindley-Milner), можно вывести основной тип всех выражений, включая надлежащее использование перегруженных методов класса (хотя, как описано в разделе "Объявления и связывания имен" , при этом могут возникнуть определенные неоднозначные перегрузки). Поэтому использование явных указаний типов (называемые сигнатурами типов) обычно является необязательным (см. разделы "Выражения" и "Объявления и связывания имен" ).
Тип forall u. Cx1 => t1 является более общим чем тип forall w. Cx2 => t2, если и только если существует подстановка S с областью определения u, такая что:
- t2 идентично S(t1).
- Всякий раз, когда cx2 выполняется в окружении класса, S(cx1) также выполняется.
Значение типа forall u. cx => t можно инстанцировать на типах s, если и только если выполняется контекст cx[s/u]. Например, рассмотрим функцию double:
double x = x + x
Наиболее общим типом для double является forall a. Num a => a -> a. double можно применять к значениям типа Int (инстанцирование a к Int), так как выполняется Num Int, потому что Int является экземпляром класса Num. Однако double обычно нельзя применять к значениям типа Char, потому что Char обычно не является экземпляром класса Num. Пользователь может предпочесть объявить такой экземпляр, в этом случае double можно действительно применить к Char.
4.2 Типы данных, определяемые пользователем
В этом разделе мы опишем алгебраические типы данных (объявления data ), переименованные типы данных (объявления newtype ) и синонимы типов (объявления type ). Эти объявления можно использовать только на верхнем уровне модуля.
4.2.1 Объявления алгебраических типов данных
объявление-верхнего-уровня | data [контекст ] простой-тип = список-конструкций [deriving-инструкция] | ||
простой-тип | конструктор-типа переменная-типа1 ... переменная-типаk | (k >= 0) | |
список-конструкций | конструкция1 | ... | конструкцияn | (n >= 1) | |
конструкция | (число аргументов конструктора con = k, k >= 0 ) | ||
| | (btype | ! atype) conop (btype | ! atype) | (инфиксный оператор conop) | |
| | con { fielddecl1 , ... , fielddecln } | (n >= 0) | |
fielddecl | vars :: (type | ! atype) | ||
deriving | deriving (dclass | (dclass1, ... , dclassn)) | (n >= 0) | |
dclass | qtycls |
Перевод:
Приоритет для constr (конструкции) - тот же, что и для выражений: применение обычного конструктора имеет более высокий приоритет, чем применение инфиксного конструктора (таким образом, a : Foo a интерпретируется при разборе как a : (Foo a) ).
Объявление алгебраического типа данных имеет вид:
data cx => T u1 ... uk = K1 t11 ... t1k1 | ...| Kn tn1 ... tnkn
где cx - контекст. Это объявление вводит новый конструктор типа T с одной или более составляющими конструкторами данных K1, ..., Kn. В этом описании неуточненный термин "конструктор" всегда означает "конструктор данных".
Типы конструкторов данных задаются следующим образом:
Ki :: forall u1 ... uk. cxi =>ti1 ->...->tiki ->(T u1 ... uk)
где cxi - наибольшее подмножество cx, которое содержит только те переменные типов, котрые являются свободными в типах ti1, ..., tiki. Переменные типов u1, ..., uk должны быть различными и могут появляться в cx и tij ; если любая другая переменная типа появится в cx или в правой части - возникнет статическая ошибка. Новая константа типа T относится к виду 1 -> ... -> k -> *, где виды i, к которым относятся переменные аргументов ui, устанавливаются с помощью выводы вида, описанного в разделе "Объявления и связывания имен" . Это означает, что T можно использовать в выражениях с типами с любым числом аргументов от 0 до k.
Например, объявление
data Eq a => Set a = NilSet | ConsSet a (Set a)
вводит конструктор типа Set, который относится к виду *->*, и конструкторы NilSet и ConsSet с типами
NilSet :: forall a. Set a ConsSet :: forall a. Eq a =>a ->Set a ->Set a
В данном примере перегруженный тип для ConsSet гарантирует, что ConsSet можно применить только к значениям типа, который является экземпляром класса Eq. Сопоставление с образцом ConsSet также приводит к ограничению Eq a. Например:
f (ConsSet a s) = a
Функция f имеет установленный с помощью вывода тип Eq a => Set a -> a. Контекст в объявлении data вообще не имеет никакого другого результата.
Видимость конструкторов типов данных (т.е. "абстрактность" типа данных) вне модуля, в котором определен тип данных, управляется посредством указания имени типа данных в списке экспорта, описанном в разделе "Модули" .
Необязательная в объявлении data часть deriving должна относиться к производным экземплярам и описана в разделе "Объявления и связывания имен" .