- •Оглавление
- •От автора
- •Структура
- •Пояснения и обозначения
- •Демонстрация кунг-фу
- •Теория Основные понятия и типы данных
- •Кортежи
- •Функции, операторы
- •Полиморфные типы данных
- •Чтение сигнатур типов
- •Простейшие функции и операторы
- •Арифметические функции
- •Логические функции
- •Списочные функции
- •Кортежные функции
- •Создание своих функций
- •Способ 1. Определение функции как выражения от параметров:
- •Способ 2. Несколько определений одной функции:
- •Способ 3. Определение функции через синоним:
- •Способ 4. Лямбда функция (анонимная функция):
- •Способ 5. Частичное применение функции:
- •Образцы и сопоставление с образцом
- •Синтаксический хлеб и синтаксический сахар
- •Условия и ограничения
- •Локальные определения
- •Двумерный синтаксис
- •Арифметические последовательности
- •Замыкания списков
- •Функциональное мышление
- •Рекурсия как основное средство
- •Ручная редукция выражений
- •Думаем функционально, шаг раз
- •Думаем функционально, шаг два: аккумуляторы
- •Реализация простейших списочных и прочих функций
- •Думаем функционально, шаг три: хвостовая рекурсия
- •Еще раз о рекурсии
- •Полезные хитрости языка
- •Ленивые вычисления и строгие функции
- •Бесконечные списки
- •Функция show
- •Совсем немного о классах
- •Функция read
- •Функция error
- •Побочные эффекты и функция trace
- •Функции высших порядков
- •Мотивация
- •Функция map
- •Функция filter
- •Композиция функций
- •Функция foldr
- •Функция foldl
- •Свертки: разбор полетов
- •Выявление общей функциональности
- •Стандартные функции высших порядков
- •Еще немного про строгие функции
- •Создание своих типов данных
- •Простые перечислимые типы данных
- •Контейнеры
- •О сравнении, отображении и прочих стандартных операциях
- •Параметрические типы данных
- •Сложные типы данных
- •Тип данных Maybe
- •Рекурсивные типы данных: списки
- •Рекурсивные типы данных: деревья
- •Ввод-вывод
- •Простейший ввод-вывод
- •Объяснение кухни
- •Пример программы, производящей нетривиальное преобразование текстового файла
- •Пример решения задачи: Поиск в пространстве состояний
- •Через массивы и последовательность промежуточных состояний
- •Решение для тех, кто не хочет разбираться сам
- •Через списки, лог истории и уникальную очередь
- •Решение для тех, кто не хочет разбираться сам
- •Задачник
- •Пояснения и обозначения
- •Лабораторная работа 1 Простейшие функции
- •Простейшие логические функции
- •Простейшие списочные функции
- •Лабораторная работа 2 Символьные функции
- •Простейшие кортежные функции
- •Теоретико-множественные операции
- •Сортировка
- •Арифметические последовательности
- •Генераторы списков
- •Лабораторная работа 4 Бесконечные списки
- •Ввод-вывод
- •Нетривиальные функции
- •Лабораторная работа 5 Простые числа и факторизация
- •Деревья
- •Деревья вычислений
- •Дополнительные задания для самостоятельной работы Задания с Project Euler
- •Простейший инструментарий Установка WinHugs и начало работы
- •Работа с интерпретатором WinHugs в интерактивном режиме
- •Команды интерпретатору
- •Работа с модулями
- •Список рекомендуемой литературы и электронных ресурсов
Полиморфные типы данных
Давайте вернемся к функции сложения и применим к ней метод внимательного взгляда.
(+) :: 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