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

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

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 Объявления алгебраических типов данных

объявление-верхнего-уровня \to data [контекст \Rightarrow ] простой-тип = список-конструкций [deriving-инструкция]
простой-тип \to конструктор-типа переменная-типа1 ... переменная-типаk (k >= 0)
список-конструкций \to конструкция1 | ... | конструкцияn (n >= 1)
конструкция \to (число аргументов конструктора con = k, k >= 0 )
| (btype | ! atype) conop (btype | ! atype) (инфиксный оператор conop)
| con { fielddecl1 , ... , fielddecln } (n >= 0)
fielddecl \to vars :: (type | ! atype)
deriving \to deriving (dclass | (dclass1, ... , dclassn)) (n >= 0)
dclass \to qtycls

Перевод:

topdecl \to data [context =>] simpletype = constrs [deriving]
simpletype \to tycon tyvar1 ... tyvark (n >= 0)
constrs \to constr1 | ... | constrn (n >= 1)
конструкция \to конструктор [!] a-тип1 ... [!] a-типk (число аргументов конструктора con = k, k >= 0)
| (b-тип | ! a-тип) оператор-конструктора (b-тип | ! a-тип) (инфиксный оператор conop) (инфиксный оператор conop)
| конструктор { объявление-поля1 , ... , объявление-поляn } (n >= 0)
объявление-поля \to список-переменных :: (тип | ! a-тип)
deriving -инструкция \to deriving (производный-класс | (производный-класс1, ... , производный-классn)) (n >= 0)
производный-класс \to квалифицированный-класс-типа

Приоритет для 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 должна относиться к производным экземплярам и описана в разделе "Объявления и связывания имен" .