
- •Оглавление
- •От автора
- •Структура
- •Пояснения и обозначения
- •Демонстрация кунг-фу
- •Теория Основные понятия и типы данных
- •Кортежи
- •Функции, операторы
- •Полиморфные типы данных
- •Чтение сигнатур типов
- •Простейшие функции и операторы
- •Арифметические функции
- •Логические функции
- •Списочные функции
- •Кортежные функции
- •Создание своих функций
- •Способ 1. Определение функции как выражения от параметров:
- •Способ 2. Несколько определений одной функции:
- •Способ 3. Определение функции через синоним:
- •Способ 4. Лямбда функция (анонимная функция):
- •Способ 5. Частичное применение функции:
- •Образцы и сопоставление с образцом
- •Синтаксический хлеб и синтаксический сахар
- •Условия и ограничения
- •Локальные определения
- •Двумерный синтаксис
- •Арифметические последовательности
- •Замыкания списков
- •Функциональное мышление
- •Рекурсия как основное средство
- •Ручная редукция выражений
- •Думаем функционально, шаг раз
- •Думаем функционально, шаг два: аккумуляторы
- •Реализация простейших списочных и прочих функций
- •Думаем функционально, шаг три: хвостовая рекурсия
- •Еще раз о рекурсии
- •Полезные хитрости языка
- •Ленивые вычисления и строгие функции
- •Бесконечные списки
- •Функция show
- •Совсем немного о классах
- •Функция read
- •Функция error
- •Побочные эффекты и функция trace
- •Функции высших порядков
- •Мотивация
- •Функция map
- •Функция filter
- •Композиция функций
- •Функция foldr
- •Функция foldl
- •Свертки: разбор полетов
- •Выявление общей функциональности
- •Стандартные функции высших порядков
- •Еще немного про строгие функции
- •Создание своих типов данных
- •Простые перечислимые типы данных
- •Контейнеры
- •О сравнении, отображении и прочих стандартных операциях
- •Параметрические типы данных
- •Сложные типы данных
- •Тип данных Maybe
- •Рекурсивные типы данных: списки
- •Рекурсивные типы данных: деревья
- •Ввод-вывод
- •Простейший ввод-вывод
- •Объяснение кухни
- •Пример программы, производящей нетривиальное преобразование текстового файла
- •Пример решения задачи: Поиск в пространстве состояний
- •Через массивы и последовательность промежуточных состояний
- •Решение для тех, кто не хочет разбираться сам
- •Через списки, лог истории и уникальную очередь
- •Решение для тех, кто не хочет разбираться сам
- •Задачник
- •Пояснения и обозначения
- •Лабораторная работа 1 Простейшие функции
- •Простейшие логические функции
- •Простейшие списочные функции
- •Лабораторная работа 2 Символьные функции
- •Простейшие кортежные функции
- •Теоретико-множественные операции
- •Сортировка
- •Арифметические последовательности
- •Генераторы списков
- •Лабораторная работа 4 Бесконечные списки
- •Ввод-вывод
- •Нетривиальные функции
- •Лабораторная работа 5 Простые числа и факторизация
- •Деревья
- •Деревья вычислений
- •Дополнительные задания для самостоятельной работы Задания с Project Euler
- •Простейший инструментарий Установка WinHugs и начало работы
- •Работа с интерпретатором WinHugs в интерактивном режиме
- •Команды интерпретатору
- •Работа с модулями
- •Список рекомендуемой литературы и электронных ресурсов
Синтаксический хлеб и синтаксический сахар
Еще немного возможностей языка, которые упрощают написание функций и делают их красивыми и самоописываемыми. Я понимаю, очень хочется, наконец, перейти к изучению заманчивых идей и самой сути функционального программирования – но тогда придется потом часто отвлекаться, чего не хотелось бы.
Условия и ограничения
max :: Ord a => a -> a -> a
max x y = if x > y then x else y
Функция, возвращающая из двух большее значение. Первая строчка определяет тип функции, но в большинстве случаев без нее можно обойтись: компилятор сможет сам вывести тип функции. При этом он постарается вывести такой тип, чтобы он подходил под как можно большее число типов. Например, тип функции max мог бы быть: Integer -> Integer -> Integer, но это означало бы, что функцию нельзя применить ни к каким другим типам.
Вот как примерно будет думать компилятор: "Так, посмотрим, что у нас внутри функции делается с двумя параметрами? Ага, к ним применяется операция сравнения (>), а какие у нее требования? Она требует любой тип a, принадлежащий классу Ord. Стало быть x и y оба принадлежат этому типу. А возвращает эта функция как раз или x, или y, значит, тот же тип является и возвращаемым".
Под всей этой с виду алхимией лежит на самом деле вполне серьезная математика, которая называется – система типов Хиндли-Милнера. Вообще, под всем функциональным программированием лежит серьезная математика – лямбда-исчисление и прочие чёрчи.
Возвращаясь к нашей функции: мы видим условную конструкцию языка. В отличие от оператора if в императивных языках, где он имеет вид if выражение then действие else действие, в языке Haskell конструкция if выражение1 then выражение2 else выражение3 – это именно выражение, значение которого равно значению выражения2 или выражения3 в зависимости от того, равно ли True выражение1. Понятно, что ветка else обязана быть в такой конструкции, а выражение2 и выражение3 должны быть одного типа.
Если условий, которые надо проверять, становится много, то конструкция из вложенных типов становится очень сложной. В таком случае удобнее пользоваться такой возможностью, как ограничения:
sign x | x > 0 = 1
| x < 0 = (-1)
| otherwise = 0
Здесь мы видим три определения одной функции, причем у последних двух левая часть определения до условия для краткости опущена. На самом деле здесь работает старое доброе сопоставление с образцом, только образец x, который раньше срабатывал всегда, теперь срабатывает, только если прилагаемое к нему условие равно True. Красивое решение, которым, я уверен, очень гордятся разработчики языка (я бы гордился на их месте) – слово "otherwise". Это совсем не какая-то особенная часть языка, как слова then или else. Где-то глубоко в стандартных библиотеках написано, оцените юмор:
otherwise = True
Так что, это просто замена такому условию, которое срабатывает всегда.
Локальные определения
Простая функция, находящая решение квадратного уравнения:
solve a b c
| d < 0 = []
| otherwise = [(-b + sqrt d)/2/a, (-b - sqrt d)/2/a] where
d = b*b - 4*a*c
Заметили это where? После него идет локальное определение имени d, используемого в теле основной функции. Можно было бы объявить глобальную функцию:
d a b c = b*b - 4*a*c
Но тогда и вызывать мы ее пришлось с параметрами, – да и пригодится ли где-то еще значение дискриминанта, кроме как внутри решения квадратного уравнения? Различных функций и констант в секции where может быть много, и все они видны только из главной функции, к которой относится where. Зато они легко могут использовать параметры главной функции, что здесь и продемонстрировано.
Есть и другая альтернатива – выражение let определения in выражение:
solve a b c =
let
d = b*b - 4*a*c
in
if d < 0 then
[]
else
[(-b + sqrt d)/2/a, (-b - sqrt d)/2/a]
Здесь мы сначала даем все нужные локальные определения, а затем уже используем их в теле функции. Еще одно отличие заключается в том, что секция where включает локальные определения, тогда как let определения in выражение само по себе является выражением, имеющим значение – то есть может подставляться в функции и появляться везде, где может быть выражение.