Создание экземпляров классов для параметризованных типов 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Создание экземпляров классов для параметризованных типов



 
 

Но как тип Maybe и списковый тип сделаны экземплярами классов? Тип Maybe отличается, скажем, от типа TrafficLight тем, что Maybe сам по себе не является конкретным типом – это конструктор ти- пов, который принимает один тип-параметр (например, Char), что- бы создать конкретный тип (как Maybe Char). Давайте посмотрим на класс Eq ещё раз:

class Eq a where

(==):: a –> a –> Bool

 
 

(/=):: a –> a –> Bool x == y = not (x /= y) x /= y = not (x == y)

Из декларации типа мы видим, что a используется как конкрет- ный тип, потому что все типы в функциях должны быть конкрет- ными (помните, мы обсуждали, что не можем иметь функцию типа a –> Maybe, но можем – функцию типа: a –> Maybe a или Maybe Int –> Maybe String). Вот почему недопустимо делать что-нибудь в таком роде:


 

instance Eq Maybe where

 
 

...

Ведь, как мы видели, идентификатор a должен принимать значе- ние в виде конкретного типа, а тип Maybe не является таковым. Это конструктор типа, который принимает один параметр и произво- дит конкретный тип.

 
 

Было бы скучно прописывать instance Eq (Maybe Int) where, instance Eq (Maybe Char) where и т. д. для всех существующих типов. Вот почему мы можем записать это так:

instance Eq (Maybe m) where Just x == Just y = x == y Nothing == Nothing = True

 
 

_ == _ = False

Это всё равно что сказать, что мы хотим сделать для всех ти- пов формата Maybe < нечто > экземпляр класса Eq. Мы даже могли бы записать (Maybe something), но обычно программисты используют одиночные буквы, чтобы придерживаться стиля языка Haskell. Вы- ражение (Maybe m) выступает в качестве типа a в декларации class Eq a where. Тип Maybe не является конкретным типом, а Maybe m – яв- ляется. Указание типа-параметра (m в нижнем регистре) свидетель- ствует о том, что мы хотим, чтобы все типы вида Maybe m, где m – лю- бой тип, имели экземпляры класса Eq.

 
 

Однако здесь есть одна проблема. Заметили? Мы используем оператор == для содержимого типа Maybe, но у нас нет уверенности, что то, что содержит тип Maybe, может быть использовано с метода- ми класса Eq. Вот почему необходимо поменять декларацию экзем- пляра на следующую:

instance (Eq m) => Eq (Maybe m) where Just x == Just y = x == y

Nothing == Nothing = True

 
 

_ == _ = False

Нам пришлось добавить ограничение на класс. Таким объяв- лением экземпляра класса мы утверждаем: необходимо, чтобы все типы вида Maybe m имели экземпляр для класса Eq, но при этом тип m (тот, что хранится в Maybe) также должен иметь экземпляр клас- са Eq. Такой же экземпляр породил бы сам язык Haskell, если бы мы воспользовались директивой deriving.


 

В большинстве случаев ограничения на класс в декларации класса используются для того, чтобы сделать класс подклассом дру- гого класса. Ограничения на класс в определении экземпляра ис- пользуются для того, чтобы выразить требования к содержимому некоторого типа. Например, в данном случае мы требуем, чтобы содержимое типа Maybe также имело экземпляр для класса Eq.

При создании экземпляров, если вы видите, что тип использо- вался как конкретный при декларации (например, a –> a –> Bool), а вы реализуете экземпляр для конструктора типов, следует предо- ставить тип-параметр и добавить скобки, чтобы получить конкрет- ный тип.

Примите во внимание, что тип, экземпляр для которого вы пытаетесь создать, заменит параметр в декларации класса. Пара- метр a из декларации class Eq a where будет заменён конкретным типом при создании экземпляра; попытайтесь в уме заменить тип также и в декларациях функций. Сигнатура (==):: Maybe –> Maybe –> Bool не имеет никакого смысла, но сигнатура (==):: (Eq m) => Maybe m –> Maybe m –> Bool имеет. Впрочем, это нужно только для уп- ражнения, потому что оператор == всегда будет иметь тип (==):: (Eq a) => a –> a –> Bool независимо от того, какие экземпляры мы порождаем.

 
 

О, и ещё одна классная фишка! Если хотите узнать, какие экземпляры существуют для класса типов, вызовите команду: info в GHCi. Например, выполнив команду:info Num, вы увидите, какие функции определены в этом классе типов, и выведете список принадлежащих классу типов. Команда:info также работает с типа- ми и конструкторами типов. Если выполнить:info Maybe, мы увидим все классы типов, к которым относится тип Maybe. Вот пример:

ghci>:info Maybe

data Maybe a = Nothing | Just a -- Defined in Data.Maybe instance Eq a => Eq (Maybe a) -- Defined in Data.Maybe instance Monad Maybe -- Defined in Data.Maybe

instance Functor Maybe -- Defined in Data.Maybe

 
 

instance Ord a => Ord (Maybe a) -- Defined in Data.Maybe instance Read a => Read (Maybe a) -- Defined in GHC.Read instance Show a => Show (Maybe a) -- Defined in GHC.Show


 

Класс типов «да–нет»

 
 

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

if (0) alert("ДА!") else alert("НЕТ!")

 
 

if ("") alert ("ДА!") else alert("НЕТ!") if (false) alert("ДА!") else alert("НЕТ!)

и все они покажут НЕТ!".

 
 

Если вызвать

if ("ЧТО") alert ("ДА!") else alert("НЕТ!")

мы увидим "ДА!", так как язык JavaScript рассматривает непустые строки как вариант истинного значения.

Несмотря на то что стро- гое использование типа Bool для булевских выражений является преимуществом языка Haskell, давайте реа- лизуем подобное поведение. Просто для забавы. Начнём с декларации класса:

 
 

class YesNo a where yesno:: a –> Bool

Довольно просто. Класс типов YesNo определяет один метод. Эта функция принимает одно значение некоторого типа, кото- рый может рассматриваться как хранитель некоей концепции истинности; функция говорит нам, истинно значение или нет. Обратите внимание: из того, как мы использовали параметр a в функции, следует, что он должен быть конкретным типом.

 
 

Теперь определим несколько экземпляров. Для чисел, так же как и в языке JavaScript, предположим, что любое ненулевое значе- ние истинно, а нулевое – ложно.

instance YesNo Int where yesno 0 = False


КЛАСС ТИПОВ «ДА–НЕТ» 191

 

 
 

yesno _ = True

Пустые списки (и, соответственно, строки) считаются имею- щими ложное значение; не пустые списки истинны.

 
 

instance YesNo [a] where yesno [] = False yesno _ = True

Обратите внимание, как мы записали тип-параметр для того, чтобы сделать список конкретным типом, но не делали никаких предположений о типе, хранимом в списке. Что ещё? Гм-м... Я знаю, что тип Bool также содержит информацию об истинности или лож- ности, и сообщает об этом довольно недвусмысленно:

 
 

instance YesNo Bool where yesno = id

Что? Какое id?.. Это стандартная библиотечная функция, кото- рая принимает параметр и его же и возвращает. Мы всё равно запи- сали бы то же самое. Сделаем экземпляр для типа Maybe:

 
 

instance YesNo (Maybe a) where yesno (Just _) = True yesno Nothing = False

Нам не нужно ограничение на класс параметра, потому что мы не делаем никаких предположений о содержимом типа Maybe. Мы говорим, что он истинен для всех значений Just и ложен для значе- ния Nothing. Нам приходится писать (Maybe a) вместо просто Maybe, потому что, если подумать, не может существовать функции Maybe –> Bool, так как Maybe – не конкретный тип; зато может существовать функция Maybe a –> Bool. Круто – любой тип вида Maybe < нечто > явля- ется частью YesNo независимо от того, что представляет собой это

«нечто»!

 
 

Ранее мы определили тип Tree для представления бинарного поискового дерева. Мы можем сказать, что пустое дерево должно быть аналогом ложного значения, а не пустое – истинного.

instance YesNo (Tree a) where yesno EmptyTree = False yesno _ = True


 

 
 

Есть ли аналоги истинности и ложности у цветов светофора? Конечно. Если цвет красный, вы останавливаетесь. Если зелёный – идёте. Ну а если жёлтый? Ну, я обычно бегу на жёлтый: жить не могу без адреналина!

instance YesNo TrafficLight where yesno Red = False

 
 

yesno _ = True

Ну что ж, мы определили несколько экземпляров, а теперь да- вайте поиграем с ними:

ghci> yesno $ length []

False

ghci> yesno "ха-ха"

True

ghci> yesno ""

False

ghci> yesno $ Just 0 True

ghci> yesno True True

ghci> yesno EmptyTree False

ghci> yesno []

False

ghci> yesno [0,0,0]

True

ghci>:t yesno

 
 

yesno:: (YesNo a) => a –> Bool

Та-ак, работает. Теперь сделаем функцию, которая работает, как оператор if, но со значениями типов, для которых есть экземпляр класса YesNo:

yesnoIf:: (YesNo y) => y –> a –> a –> a yesnoIf yesnoVal yesResult noResult =

 
 

if yesno yesnoVal then yesResult else noResult

Всё довольно очевидно. Функция принимает значение для оп- ределения истинности и два других параметра. Если значение ис- тинно, возвращается первый параметр; если нет – второй.


 

ghci> yesnoIf [] "ДА!" "НЕТ!" "НЕТ!"

ghci> yesnoIf [2,3,4] "ДА!" "НЕТ!" "ДА!"

ghci> yesnoIf True "ДА!" "НЕТ!" "ДА!"

ghci> yesnoIf (Just 500) "ДА!" "НЕТ!" "ДА!"

 
 

ghci> yesnoIf Nothing "ДА!" НЕТ!" НЕТ!"

 

Класс типов Functor

Мы уже встречали множество классов типов из стандартной библиотеки. Ознакомились с классом Ord, предус- мотренным для сущностей, которые можно упорядочить. Вдоволь наба- ловались с классом Eq, предназна- ченным для сравнения на равенство. Изучили класс Show, предоставляю- щий интерфейс для типов, которые можно представить в виде строк. Наш добрый друг класс Read помо- гает, когда нам надо преобразовать строку в значение некоторого типа. Ну а теперь приступим к рассмотре- нию класса типов Functor, предназна- ченного для типов, которые могут быть отображены друг в друга.

Возможно, в этот момент вы по- думали о списках: ведь отображение списков – это очень распространён-

ная идиома в языке Haskell. И вы правы: списковый тип имеет эк- земпляр для класса Functor.

 
 

Нет лучшего способа изучить класс типов Functor, чем посмот- реть, как он реализован. Вот и посмотрим:

fmap:: (a -> b) -> f a -> f b


 

Итак, что у нас имеется? Класс определяет одну функцию fmap и не предоставляет для неё реализации по умолчанию. Тип функ- ции fmap весьма интересен. Во всех вышеприведённых определе- ниях классов типов тип-параметр, игравший роль типа в классе, был некоторого конкретного типа, как переменная a в сигнатуре (==):: (Eq a) => a –> a –> Bool. Но теперь тип-параметр f не имеет конкретного типа (нет конкретного типа, который может при- нимать переменная, например Int, Bool или Maybe String); в этом случае переменная – конструктор типов, принимающий один па- раметр. (Напомню: выражение Maybe Int является конкретным типом, а идентификатор Maybe – конструктор типов с одним па- раметром.) Мы видим, что функция fmap принимает функцию из одного типа в другой и функтор, применённый к одному типу, и возвращает функтор, применённый к другому типу.

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

 
 

Гм-м... что-то мне напоминает объявление функции fmap! Если вы не знаете сигнатуру функции map, вот она:

map:: (a –> b) –> [a] –> [b]

 
 

О, как интересно! Функция map берёт функцию из a в b и список элементов типа а и возвращает список элементов типа b. Друзья, мы только что обнаружили функтор! Фактически функция map – это функция fmap, которая работает только на списках. Вот как список сделан экземпляром класса Functor:

instance Functor [] where fmap = map

 
 

И всё! Заметьте, мы не пишем instance Functor [a] where, потому что из определения функции

fmap:: (a –> b) –> f a –> f b

мы видим, что параметр f должен быть конструктором типов, принимающим один тип. Выражение [a] – это уже конкретный тип (список элементов типа а), а вот [] – это конструктор типов, который принимает один тип; он может производить такие конк- ретные типы, как [Int], [String] или даже [[String]].

Так как для списков функция fmap – это просто map, то мы полу- чим одинаковые результаты при их использовании на списках:


 

map:: (a –> b) –> [a] –> [b] ghci>fmap (*2) [1..3]

[2,4,6]

 
 

ghci> map (*2) [1..3] [2,4,6]

Что случится, если применить функцию map или fmap к пустому списку? Мы получим опять же пустой список. Но функция fmap преобразует пустой список типа [a] в пустой список типа [b].

 



Поделиться:


Последнее изменение этой страницы: 2017-02-17; просмотров: 176; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.134.118.95 (0.054 с.)