Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
MakeevGA-Haskell-a4-shortcode_2014_05_31.doc
Скачиваний:
15
Добавлен:
19.01.2023
Размер:
1.79 Mб
Скачать

Полиморфные типы данных

Давайте вернемся к функции сложения и применим к ней метод внимательного взгляда.

(+) :: Float -> Float -> Float

А если надо сложить два целых числа? Какую-то другую функцию использовать? А как насчет функции length, находящей длину списка символов?

length :: [Char] -> Int

А если надо найти длину списка целых чисел? Или длину списка значений типа Bool? Или длину списка списков Integer? Каждый раз разные функции использовать?

Давайте заглянем вперед и узнаем, как в действительности написана функция length:

length [] = 0

length xs = 1 + length (tail xs)

Код этой функции описывает сам себя: длина пустого списка считается нулевой, а длина любого другого списка считается как увеличенная на единицу длина исходного списка без первого элемента (функция tail возвращает все элементы списка, кроме первого).

Попробуйте найти, где в этой функции есть указание на то, со списком чего она должна работать? Нигде! И действительно, эта функция работает с любыми списками – и со списками чисел, и со списками символов, и со списками строк, и со списками списков кортежей, – и даже со списками функций!

Но как же тогда описать ее тип? Для этого используется понятие "переменная типа":

length :: [a] -> Int

Здесь a – это как раз переменная типа, то есть нечто, под чем может скрываться абсолютно любой тип. И фразу эту мы тогда прочитаем так: length – это функция, берущая список значений произвольного типа a и возвращающая значение типа Int.

Вернемся в третий раз к функции сложения и запишем ее аналогично функции length:

(+) :: a -> a -> a

Но разве можно применять числовое сложение, например, к кортежам? Или, например, к функциям? Разве имеет, например, смысл выражение sin + cos? Обратите внимание, что мы пытаемся складывать две функции, а не два значения Float, как было бы в случае выражения sin 5 + cos 5, которое вполне осмысленно.

Получается, с одной стороны, мы не хотим ограничивать операцию сложения каким-то одним типом, а с другой стороны, вседозволенность этой функции точно не присуща. С какими типами должна оперировать функция сложения? Ответ на самом деле прост – с числовыми! Самый правильный тип этой функции выглядит так:

(+) :: Num a => a -> a -> a

И читается эта запись следующим образом: функция сложения берет значение какого-то типа a, затем берет еще одно значение того же типа a и возвращает значение того же самого типа a, где тип a принадлежит классу численных типов Num.

Вот оно что: оказывается, можно типы данных сгруппировать в классы типов по тому, какие операции к ним применимы!

А как насчет операции сравнения? Должна она позволять сравнивать только числа? А может быть, только символы?

(>) :: Integer -> Integer -> Bool ?

(>) :: Float -> Float -> Bool ?

(>) :: Char -> Char -> Bool ?

С какими типами она должна работать? Может быть, с любыми?

(>) :: a -> a -> Bool ?

Я думаю, вы уже догадались: она должна работать только с такими типами данных, которые допускают сравнение значений на больше-меньше, и этот факт отражается в использовании класса типов Ord:

(>) :: Ord a => a -> a -> Bool

Не спутайте этот класс Ord с функцией ord :: Char -> Int, которая нам уже попадалась раньше. Они никак не связаны, это просто совпадение. Класс Ord – это класс таких типов, которые допускают сравнение на больше-меньше.

Аналогичным образом, операция проверки равенства двух значений должна оперировать только такими типами, которые допускают проверку равенства:

(==) :: Eq a => a -> a -> Bool