ТОП 10:

Несколько заключительных слов о классах типов



Поскольку класс типа определяет абстрактный интерфейс, один и тот же тип данных может иметь экземпляры для различных клас- сов, а для одного и того же класса могут быть определены экзем- пляры различных типов. Например, тип Char имеет экземпляры для многих классов, два из которых – Eq и Ord, поскольку мы можем сравнивать символы на равенство и располагать их в алфавитном порядке.

Иногда для типа данных должен быть определён экземпляр не- которого класса для того, чтобы имелась возможность определить для него экземпляр другого класса. Например, для определения эк- земпляра класса Ord необходимо предварительно иметь экземпляр класса Eq. Другими словами, наличие экземпляра класса Eq является предварительным (необходимым) условием для определения экземпля- ра класса Ord. Если поразмыслить, это вполне логично: раз уж до- пускается расположение неких значений в определённом порядке, то должна быть предусмотрена и возможность проверить их на ра- венство.


 

СИНТАКСИС ФУНКЦИЙ

 

Сопоставление с образцом

В этой главе будет рассказано о неко- торых весьма полезных синтаксических конструкциях языка Haskell, и начнём мы с сопоставления с образцом. Идея за- ключается в указании определённых шаб- лонов – образцов, которым должны соот- ветствовать некоторые данные. Во время выполнения программы данные проверя- ются на соответствие образцу (сопостав- ляются). Если они подходят под образец, то будут разобраны в соответствии с ним. Когда вы определяете функцию, её определение можно разбить на несколь- ко частей (клозов), по одной части для каждого образца. Это позволяет создать очень стройный код, простой и легко чи-

 
 

таемый. Вы можете задавать образцы для любого типа данных – чи- сел, символов, списков, кортежей и т. д. Давайте создадим простую функцию, которая проверяет, является ли её параметр числом семь.

lucky :: Int -> String

lucky 7 = "СЧАСТЛИВОЕ ЧИСЛО 7!"

 
 

lucky x = "Прости, друг, повезёт в другой раз!"


 

Когда вы вызываете функцию lucky, производится проверка па- раметра на совпадение с заданными образцами в том порядке, в ка- ком они были заданы. Когда проверка даст положительный резуль- тат, используется соответствующее тело функции. Единственный случай, когда число, переданное функции, удовлетворяет первому образцу, – когда оно равно семи. В противном случае проводится проверка на совпадение со следующим образцом. Следующий об- разец может быть успешно сопоставлен с любым числом; также он привязывает переданное число к переменной x.

Если в образце вместо реального значения (например, 7) пишут идентификатор, начинающийся со строчной буквы (например, x, y или myNumber), то этот образец будет сопоставлен любому передан- ному значению. Обратиться к сопоставленному значению в теле функции можно будет посредством введённого идентификатора. Эта функция может быть реализована с использованием клю- чевого слова if. Ну а если нам потребуется написать функцию, ко- торая называет цифры от 1 до 5 и выводит "Это число не в пределах от 1 до 5" для других чисел? Без сопоставления с образцом нам бы пришлось создать очень запутанное дерево условных выражений if – then – else. А вот что получится, если использовать сопостав-

 
 

ление:

sayMe :: Int -> String sayMe 1 = "Один!" sayMe 2 = "Два!"

sayMe 3 = "Три!" sayMe 4 = "Четыре!" sayMe 5 = "Пять!"

 
 

sayMe x = "Это число не в пределах от 1 до 5"

Заметьте, что если бы мы переместили последнюю строку опре- деления функции (образец в которой соответствует любому вводу) вверх, то функция всегда выводила бы "Это число не в пределах от 1 до 5", потому что невозможно было бы пройти дальше и провести проверку на совпадение с другими образцами.

Помните реализованную нами функцию факториала? Мы оп- ределили факториал числа n как произведение чисел [1..n]. Мы можем определить данную функцию рекурсивно, точно так же, как факториал определяется в математике. Начнём с того, что объявим факториал нуля равным единице.


 

 
 

Затем определим факториал любого положительного числа как данное число, умноженное на факториал предыдущего числа. Вот как это будет выглядеть в терминах языка Haskell.

factorial :: Integer -> Integer factorial 0 = 1

 
 

factorial n = n * factorial (n – 1)

Мы в первый раз задали функцию рекурсивно. Рекурсия очень важна в языке Haskell, и подробнее она будет рассмотрена позже.

 
 

Сопоставление с образцом может завершиться неудачей, если мы зададим функцию следующим образом:

charName :: Char –> String charName 'а' = "Артём" charName 'б' = "Борис" charName 'в' = "Виктор"

 
 

а затем попытаемся вызвать её с параметром, которого не ожидали. Произойдёт следующее:

ghci> charName 'а' "Артём"

ghci> charName 'в' "Виктор"

ghci> charName 'м'

 
 

"*** Exception: Non-exhaustive patterns in function charName

Это жалоба на то, что наши образцы не покрывают всех возмож- ных случаев (недоопределены) – и, воистину, так оно и есть! Когда мы определяем функцию, мы должны всегда включать образец, кото- рый можно сопоставить с любым входным значением, для того что- бы наша программа не закрывалась с сообщением об ошибке, если функция получит какие-то непредвиденные входные данные.

 

Сопоставление с парами

Сопоставление с образцом может быть использовано и для корте- жей. Что если мы хотим создать функцию, которая принимает два двумерных вектора (представленных в форме пары) и складывает их? Чтобы сложить два вектора, нужно сложить их соответствую- щие координаты. Вот как мы написали бы такую функцию, если б не знали о сопоставлении с образцом:


 

 
 

addVectors :: (Double, Double) -> (Double, Double) -> (Double, Double) addVectors a b = (fst a + fst b, snd a + snd b)

Это, конечно, сработает, но есть способ лучше. Давайте испра- вим функцию, чтобы она использовала сопоставление с образцом:

 
 

addVectors :: (Double, Double) -> (Double, Double) -> (Double, Double) addVectors (x1, y1) (x2, y2) = (x1 + x2, y1 + y2)

Так гораздо лучше. Теперь ясно, что параметры функции яв- ляются кортежами; к тому же компонентам кортежа сразу даны имена – это повышает читабельность. Заметьте, что мы сразу напи- сали образец, соответствующий любым значениям. Тип функции addVectors в обоих случаях совпадает, так что мы гарантированно получим на входе две пары:

ghci> :t addVectors

 
 

addVectors :: (Double, Double) -> (Double, Double) -> (Double, Double)

Функции fst и snd извлекают компоненты пары. Но как быть с тройками? Увы, стандартных функций для этой цели не сущест- вует, однако мы можем создать свои:

first :: (a, b, c) –> a first (x, _, _) = x

second :: (a, b, c) –> b second (_, y, _) = y

 
 

third :: (a, b, c) –> c third (_, _, z) = z

Символ _ имеет то же значение, что и в генераторах списков. Он означает, что нам не интересно значение на этом месте, так что мы просто пишем _.

 







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

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