- •Оглавление
- •От автора
- •Структура
- •Пояснения и обозначения
- •Демонстрация кунг-фу
- •Теория Основные понятия и типы данных
- •Кортежи
- •Функции, операторы
- •Полиморфные типы данных
- •Чтение сигнатур типов
- •Простейшие функции и операторы
- •Арифметические функции
- •Логические функции
- •Списочные функции
- •Кортежные функции
- •Создание своих функций
- •Способ 1. Определение функции как выражения от параметров:
- •Способ 2. Несколько определений одной функции:
- •Способ 3. Определение функции через синоним:
- •Способ 4. Лямбда функция (анонимная функция):
- •Способ 5. Частичное применение функции:
- •Образцы и сопоставление с образцом
- •Синтаксический хлеб и синтаксический сахар
- •Условия и ограничения
- •Локальные определения
- •Двумерный синтаксис
- •Арифметические последовательности
- •Замыкания списков
- •Функциональное мышление
- •Рекурсия как основное средство
- •Ручная редукция выражений
- •Думаем функционально, шаг раз
- •Думаем функционально, шаг два: аккумуляторы
- •Реализация простейших списочных и прочих функций
- •Думаем функционально, шаг три: хвостовая рекурсия
- •Еще раз о рекурсии
- •Полезные хитрости языка
- •Ленивые вычисления и строгие функции
- •Бесконечные списки
- •Функция show
- •Совсем немного о классах
- •Функция read
- •Функция error
- •Побочные эффекты и функция trace
- •Функции высших порядков
- •Мотивация
- •Функция map
- •Функция filter
- •Композиция функций
- •Функция foldr
- •Функция foldl
- •Свертки: разбор полетов
- •Выявление общей функциональности
- •Стандартные функции высших порядков
- •Еще немного про строгие функции
- •Создание своих типов данных
- •Простые перечислимые типы данных
- •Контейнеры
- •О сравнении, отображении и прочих стандартных операциях
- •Параметрические типы данных
- •Сложные типы данных
- •Тип данных Maybe
- •Рекурсивные типы данных: списки
- •Рекурсивные типы данных: деревья
- •Ввод-вывод
- •Простейший ввод-вывод
- •Объяснение кухни
- •Пример программы, производящей нетривиальное преобразование текстового файла
- •Пример решения задачи: Поиск в пространстве состояний
- •Через массивы и последовательность промежуточных состояний
- •Решение для тех, кто не хочет разбираться сам
- •Через списки, лог истории и уникальную очередь
- •Решение для тех, кто не хочет разбираться сам
- •Задачник
- •Пояснения и обозначения
- •Лабораторная работа 1 Простейшие функции
- •Простейшие логические функции
- •Простейшие списочные функции
- •Лабораторная работа 2 Символьные функции
- •Простейшие кортежные функции
- •Теоретико-множественные операции
- •Сортировка
- •Арифметические последовательности
- •Генераторы списков
- •Лабораторная работа 4 Бесконечные списки
- •Ввод-вывод
- •Нетривиальные функции
- •Лабораторная работа 5 Простые числа и факторизация
- •Деревья
- •Деревья вычислений
- •Дополнительные задания для самостоятельной работы Задания с Project Euler
- •Простейший инструментарий Установка WinHugs и начало работы
- •Работа с интерпретатором WinHugs в интерактивном режиме
- •Команды интерпретатору
- •Работа с модулями
- •Список рекомендуемой литературы и электронных ресурсов
О сравнении, отображении и прочих стандартных операциях
Мы создали тип данных, Point, однако первая же попытка распечатать в интерпретаторе значение этого типа приведет к ошибке:
SomeModule> Pt 1 2
ERROR - Cannot find "show" function for:
*** Expression : Pt 1 2
*** Of type : Point
Помните про функцию show и класс Show? Претензии интерпретатора понятны: он не знает, как своей функцией show отобразить наш созданный тип данных. Похожая проблема возникнет, если мы попробуем сравнить два значения нашего типа Point. Позже я покажу, как эту проблему решать в общем случае, а пока воспользуемся магией:
data Point = Pt {px :: Integer, py :: Integer}
deriving (Eq, Show)
Вот этот маленький довесок нам очень облегчит жизнь. Он позволит сравнивать значения типа Point между собой и видеть значения этого типа в интерпретаторе. Смысл этой магии в том, что мы просим интерпретатор как-нибудь самостоятельно придумать, как сравнивать и отображать значения этого типа:
SomeModule> Pt 1 2
Pt{px=1,py=2}
Параметрические типы данных
Рассмотренный тип данных, Point, представляет собой точку с целочисленными координатами. А если мы захотим нецелочисленные координаты – создавать новый тип и переписывать все функции? Для того, чтобы этого избежать, можно сразу создать параметризованные типы данных:
data Point a = Pt a a deriving (Eq, Show)
Как видим, вместо конкретного типа Integer, мы использовали какое-то a, которое в данном случае будет называться – переменная типа. По сути, под a может скрываться любой тип – и Float, и Integer, и даже Char. Что такое здесь Pt? Это бинарный конструктор данных: он берет кусочки данных и создает из них сложные данные. Что такое Point теперь? Это унарный конструктор типа: он берет какой-то тип a и создает на его основе сложный тип Point a:
Pt :: a -> a -> Point a
Pt 1 2 :: Point Integer
Pt 1.0 2.0 :: Point Float
Pt 'a' 'b' :: Point Char
Кстати, вместо Pt можно использовать то же самое слово Point. Это разрешено, потому что имена конструкторов типов и конструкторов данных находятся в разных пространствах имен. Короче говоря, интерпретатор всегда поймет, о чем идет речь – о конструкторе типа или о конструкторе данных, поэтому они могут иметь одинаковые имена.
Кстати, а что теперь с нашей функцией квадрата расстояния:
sqdist :: Num a => Point a -> Point a -> a
sqdist (Pt x1 y1) (Pt x2 y2) = ((x1-x2)^2 + (y1-y2)^2)
Вот какой у нее теперь оказался тип: она будет работать только с точками, у которых координаты имеют численный тип. Что логично, раз функция эти координаты вычитает и возводит в квадрат.
Сложные типы данных
Теперь сведя в кучу все рассмотренные возможности, мы создадим новый тип данных – фигура, которая может быть точкой, а может быть и кругом:
data Shape =
Point {x :: Float, y :: Float} |
Circle {x :: Float, y :: Float, r :: Float}
deriving (Eq, Show)
О чем говорит эта запись? О том, что значение нового типа данных Shape может быть или точкой, и тогда внутри нее будут храниться только координаты, или кругом, и тогда внутри будут храниться координаты и радиус:
Point 1 1 :: Shape
Circle 1 1 2 :: Shape
Раз мы задали имена полям, то автоматически создано несколько функций для доступа к ним. Причем для всех полей – хотя, конечно, вызов функции r от точки приведет к ошибке.
x :: Shape -> Float
y :: Shape -> Float
r :: Shape -> Float
В большинстве случаев эти функции нам не понадобятся, так как работать с объектами мы будем с помощью образцов. Давайте, например, напишем функцию, которая будет находить площадь любого объекта типа Shape. Вспомним, как мы говорили раньше: чтобы описать функцию над определенным типом, нужно описать, как эта функция работает со всеми возможными значениями этого типа.
Сейчас у нас возможных значений (возможных точек и кругов) бесконечное число, но все они – или точки, или круги. Поэтому нам необходимо и достаточно описать, как требуемая функция будет работать в случае каждого из двух возможных способов организации значений Shape. То бишь, проще говоря – как вычислять площадь точек, а как площадь кругов:
square :: Shape -> Float
square (Point _ _) = 0
square (Circle _ _ r) = 3.14 * r^2
Прочитаем это определение функции мы так: требуется вычислить площадь данной фигуры. Если это точка с любыми координатами, то это строго ноль. Если это круг с любыми координатами и радиусом r, то площадь его равна значению вот этого выражения.