Заглавная страница Избранные статьи Случайная статья Познавательные статьи Новые добавления Обратная связь КАТЕГОРИИ: АрхеологияБиология Генетика География Информатика История Логика Маркетинг Математика Менеджмент Механика Педагогика Религия Социология Технологии Физика Философия Финансы Химия Экология ТОП 10 на сайте Приготовление дезинфицирующих растворов различной концентрацииТехника нижней прямой подачи мяча. Франко-прусская война (причины и последствия) Организация работы процедурного кабинета Смысловое и механическое запоминание, их место и роль в усвоении знаний Коммуникативные барьеры и пути их преодоления Обработка изделий медицинского назначения многократного применения Образцы текста публицистического стиля Четыре типа изменения баланса Задачи с ответами для Всероссийской олимпиады по праву Мы поможем в написании ваших работ! ЗНАЕТЕ ЛИ ВЫ?
Влияние общества на человека
Приготовление дезинфицирующих растворов различной концентрации Практические работы по географии для 6 класса Организация работы процедурного кабинета Изменения в неживой природе осенью Уборка процедурного кабинета Сольфеджио. Все правила по сольфеджио Балочные системы. Определение реакций опор и моментов защемления |
Создание экземпляров классов для параметризованных типов
Но как тип 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 с.) |