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

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

4.3.2. Объявления экземпляров

topdecl \to instance [scontext =>] qtycls inst [where idecls]
inst \to gtycon
| ( gtycon tyvar1 ... tyvark ) (k >= 0, все tyvar различны)
| ( tyvar1 , ... , tyvark ) (k >= 2, все tyvar различны)
| [ tyvar ]
| ( tyvar1 -> tyvar2 ) (tyvar1 и tyvar2 различны)
idecls \to { idecl1 ; ... ; idecln } (n >= 0)
idecl \to (funlhs | var) rhs
| (пусто)
объявление-верхнего-уровня \to instance [простой-контекст \Rightarrow ] квалифицированный-класс-типа экземпляр [ where список-объявлений-экземпляров]
экземпляр \to общий-конструктор-типа
| ( общий-конструктор-типа переменная-типа1 ... переменная-типаk ) ( k >=0, все переменные-типа различны)
| ( переменная-типа1 , ... , переменная-типаk ) ( k >=2, все переменные-типа различны)
| [ переменная-типа ]
| ( переменная-типа1 \to переменная-типа2) (переменная-типа1 и переменная-типа2 различны)
список-объявлений-экземпляров \to { объявление-экземпляра1 ; ... ; объявление-экземпляраn } (n >= 0)
объявление-экземпляра \to (левая-часть-функции | переменная) правая-часть
| (пусто)

Объявление экземпляра вводит экземпляр класса. Пусть

class cx => C u where { cbody }

является объявлением class. Тогда соответствующее объявление экземпляра в общем виде:

instance cx' => C (T u1 ... uk) where { d }

где k>=0. Тип (T u1 ... uk) должен иметь вид конструктора типа T, примененного к простым переменным типа u1, ... uk ; кроме того, T не должен являться синонимом типа, и все ui должны быть различными.

Поэтому такие объявления экземпляров запрещены:

instance C (a,a) where ...
  instance C (Int,a) where ...
  instance C [[a]] where ...

Объявления d могут содержать связывания имен только для методов класса C. Неправильно задавать связывание имен для метода класса, который не находится в области видимости, но имя, в области которого он находится, несущественно; в частности, это может быть имя с квалификатором. (Это правило идентично тому, что используется для подчиненных имен в списках экспорта, см. раздел 5.2.) Например, это допустимо, даже если range находится в области видимости только с именем с квалификатором Ix.range.

module A where
    import qualified Ix

    instance Ix.Ix T where
      range = ...

Объявления не могут содержать сигнатуры типов или infix-объявления, поскольку они уже были заданы в объявлении class. Как и в случае методов класса по умолчанию (раздел "Объявления и связывания имен" ), объявления методов должны иметь вид переменной или определения функции.

Если для некоторого метода класса не заданы связывания имен, тогда используется соответствующий метод класса по умолчанию, заданный в объявлении class (если таковое имеется); если такой метод по умолчанию не существует, тогда метод класса этого экземпляра связывается с undefined, и ошибка компиляции не возникнет.

Объявление instance, которое объявляет тип T экземпляром класса C, называется объявлением экземпляра C-T и подчиняется следующим статическим ограничениям:

  • Тип не может быть объявлен экземпляром конкретного класса более одного раза в программе.
  • Класс и тип должны относиться к одному и тому же виду, его можно установить, используя вывод вида, описанный в разделе "Объявления и связывания имен" .
  • Предположим, что переменные типов в экземпляре типа (T u1 ... uk) удовлетворяют ограничениям в контексте экземпляра cx'. При этом предположении также должны выполняться следующие два условия:
    1. Ограничения, выраженные контекстом cx[(T u1 ... uk)/u] суперкласса C, должны выполняться. Другими словами, T должен быть экземпляром каждого из суперклассов класса C, и контексты всех экземпляров суперклассов должны следовать из cx'.
    2. Любые ограничения на переменные типов в типе экземпляра, которые требуют, чтобы объявления методов класса в d были хорошо типизированы, также должны выполняться.

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

Следующий пример иллюстрирует ограничения, налагаемые экземплярами суперкласса:

class Foo a => Bar a where ...
  
  instance (Eq a, Show a) => Foo [a] where ...
  
  instance Num a => Bar [a] where ...

Этот пример является правильным в Haskell. Так как Foo является суперклассом Bar, второе объявление экземпляра будет правильным, только если [a] является экземпляром Foo исходя из предположения Num a. Первое объявление экземпляра действительно сообщает, что [a] является экземпляром Foo исходя из этого предположения, потому что Eq и Show являются суперклассами Num.

Если, вместо этого, объявления двух экземпляров оказались бы подобны этим:

instance Num a => Foo [a] where ...
  
  instance (Eq a, Show a) => Bar [a] where ...

тогда программа была бы неправильной. Объявление второго экземпляра будет правильным, только если [a] является экземпляром Foo исходя из предположений (Eq a, Show a). Но это не выполняется, так как [a] является экземпляром Foo только исходя из более сильного предположения Num a.

Дополнительные примеры объявлений instance можно найти в лекции "Стандартное начало (Prelude)" .

4.3.3 Производные экземпляры

Как упомянуто в разделе "Объявления и связывания имен" , объявления data и newtype содержат необязательную форму deriving. Если форма включена, то для каждого из названных классов типов данных автоматически генерируются объявления производных экземпляров. Эти экземпляры подчинены тем же самым ограничениям, что и определяемые пользователем экземпляры. При выведении класса C для типа T, для T должны существовать экземпляры для всех суперклассов класса C, либо посредством явного объявления instance, либо посредством включения суперкласса в инструкцию deriving.

Производные экземпляры предоставляют удобные, широко используемые операции для определяемых пользователем типов данных. Например, производные экземпляры для типов данных в классе Eq определяют операции == и /=, освобождая программиста от необходимости определять их.

Классами в Prelude, для которых разрешены производные экземпляры, являются: Eq, Ord, Enum, Bounded, Show и Read. Все они приведены в п.6.3.2. Точные детали того, как генерируются производные экземпляры для каждого из этих классов, даны в лекции "Спецификация производных экземпляров" , включая подробное описание того, когда такие производные экземпляры возможны. Классы, определенные стандартными библиотеками, также можно использовать для порождения производных.

Если невозможно произвести объявление instance над классом, названным в форме deriving, - возникнет статическая ошибка. Например, не все типы данных могут должным образом поддерживать методы класса Enum. Также статическая ошибка возникнет в случае, если задать явное объявление instance для класса, который также является производным.

Если форма deriving в объявлении data или newtype опущена, то никакие объявления экземпляров для этого типа данных не производятся, то есть отсутствие формы deriving эквивалентно включению пустой формы: deriving ().

4.3.4 Неоднозначные типы и значения по умолчанию для перегруженных числовых операций

topdecl \to default (type1 , ... , typen) (n >= 0)

Перевод:

объявление-верхнего-уровня \to default (тип1 , ... , типn) (n >= 0)

Проблема, связанная с перегрузкой в Haskell , состоит в возможности неоднозначного типа. Например, возьмем функции read и show, определенные в лекции "Спецификация производных экземпляров" , и предположим, что именно Int и Bool являются членами Read и Show, тогда выражение

let x = read "..." in show x - неправильно

является неоднозначным, потому что условия, налагаемые на типы для show и read

show :: forall a. Show a =>a ->String
read :: forall a. Read a =>String ->a

можно выполнить путем инстанцирования a как Int или Bool в обоих случаях. Такие выражения считаются неправильно типизированными, возникнет статическая ошибка.

Мы говорим, что выражение e имеет неодназначный тип, если в его типе forall u. cx => t есть переменная типа u в u, которая встречается в cx, но не в t. Такие типы недопустимы.

Например, ранее рассмотренное выражение, включающее show и read, имеет неоднозначный тип, так как его тип forall a. Show a, Read a =>String.

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

let x = read "..." in show (x::Bool)

Это устраняет неоднозначность типа.

Иногда предпочтительнее, чтобы неоднозначное выражение было того же типа, что и некоторая переменная, а не заданного с помощью сигнатуры фиксированного типа. В этом состоит назначение функции asTypeOf (лекция "Стандартное начало (Prelude)" ): x 'asTypeOf' y имеет значение x, но заставляет x и y иметь один и тот же тип. Например,

approxSqrt x = encodeFloat 1 (exponent x `div` 2) `asTypeOf` x

(Описание encodeFloat и exponent см. в разделе "Предопределенные типы и классы" .)

Неоднозначности в классе Num наиболее распространены, поэтому Haskell предоставляет другой способ разрешить их - с помощью default -объявления:

default (t1 , ... , tn)

где n>=0, и каждая ti должна иметь тип, для которого выполняется Num ti. В ситуациях, когда обнаружен неоднозначный тип, переменная неоднозначного типа v является умолчательной, если:

  • v появляется только в ограничениях вида C v, где C - класс, и,
  • по меньшей мере, один из этих классов является числовым классом (то есть Num или подклассом Num), и
  • все эти классы определены в Prelude или стандартной библиотеке.

Каждая умолчательная переменная замещается первым типом в default-списке, который является экземпляром всех классов неоднозначной переменной. Если такой тип не будет найден - возникнет статическая ошибка.

В модуле может быть только одно default-объявление, и его влияние ограничено этим модулем. Если в модуле default-объявление не задано, то предполагается, что задано объявление

default (Integer, Double)

Пустое default-объявление default () отменяет все значения по умолчанию в модуле.