- •Оглавление
- •От автора
- •Структура
- •Пояснения и обозначения
- •Демонстрация кунг-фу
- •Теория Основные понятия и типы данных
- •Кортежи
- •Функции, операторы
- •Полиморфные типы данных
- •Чтение сигнатур типов
- •Простейшие функции и операторы
- •Арифметические функции
- •Логические функции
- •Списочные функции
- •Кортежные функции
- •Создание своих функций
- •Способ 1. Определение функции как выражения от параметров:
- •Способ 2. Несколько определений одной функции:
- •Способ 3. Определение функции через синоним:
- •Способ 4. Лямбда функция (анонимная функция):
- •Способ 5. Частичное применение функции:
- •Образцы и сопоставление с образцом
- •Синтаксический хлеб и синтаксический сахар
- •Условия и ограничения
- •Локальные определения
- •Двумерный синтаксис
- •Арифметические последовательности
- •Замыкания списков
- •Функциональное мышление
- •Рекурсия как основное средство
- •Ручная редукция выражений
- •Думаем функционально, шаг раз
- •Думаем функционально, шаг два: аккумуляторы
- •Реализация простейших списочных и прочих функций
- •Думаем функционально, шаг три: хвостовая рекурсия
- •Еще раз о рекурсии
- •Полезные хитрости языка
- •Ленивые вычисления и строгие функции
- •Бесконечные списки
- •Функция show
- •Совсем немного о классах
- •Функция read
- •Функция error
- •Побочные эффекты и функция trace
- •Функции высших порядков
- •Мотивация
- •Функция map
- •Функция filter
- •Композиция функций
- •Функция foldr
- •Функция foldl
- •Свертки: разбор полетов
- •Выявление общей функциональности
- •Стандартные функции высших порядков
- •Еще немного про строгие функции
- •Создание своих типов данных
- •Простые перечислимые типы данных
- •Контейнеры
- •О сравнении, отображении и прочих стандартных операциях
- •Параметрические типы данных
- •Сложные типы данных
- •Тип данных Maybe
- •Рекурсивные типы данных: списки
- •Рекурсивные типы данных: деревья
- •Ввод-вывод
- •Простейший ввод-вывод
- •Объяснение кухни
- •Пример программы, производящей нетривиальное преобразование текстового файла
- •Пример решения задачи: Поиск в пространстве состояний
- •Через массивы и последовательность промежуточных состояний
- •Решение для тех, кто не хочет разбираться сам
- •Через списки, лог истории и уникальную очередь
- •Решение для тех, кто не хочет разбираться сам
- •Задачник
- •Пояснения и обозначения
- •Лабораторная работа 1 Простейшие функции
- •Простейшие логические функции
- •Простейшие списочные функции
- •Лабораторная работа 2 Символьные функции
- •Простейшие кортежные функции
- •Теоретико-множественные операции
- •Сортировка
- •Арифметические последовательности
- •Генераторы списков
- •Лабораторная работа 4 Бесконечные списки
- •Ввод-вывод
- •Нетривиальные функции
- •Лабораторная работа 5 Простые числа и факторизация
- •Деревья
- •Деревья вычислений
- •Дополнительные задания для самостоятельной работы Задания с Project Euler
- •Простейший инструментарий Установка WinHugs и начало работы
- •Работа с интерпретатором WinHugs в интерактивном режиме
- •Команды интерпретатору
- •Работа с модулями
- •Список рекомендуемой литературы и электронных ресурсов
Стандартные функции высших порядков
В стандартный набор библиотек входит много достаточно общих функций высших порядков. Я их приведу сейчас вместе с определениями, но самое главное в этих функциях, конечно – это не их код: он тривиален. Самое главное – идея достаточно общего поведения, которое кто-то заметил и оформил в функцию высших порядков:
takeWhile :: (a -> Bool) -> [a] -> [a]
takeWhile p [] = []
takeWhile p (x:xs)
| p x = x : takeWhile p xs
| otherwise = []
dropWhile :: (a -> Bool) -> [a] -> [a]
dropWhile p [] = []
dropWhile p (x:xs)
| p x = dropWhile p xs
| otherwise = (x:xs)
Обычные функции take и drop брали или, соответственно, выбрасывали из списка заданное количество элементов, а функция filter берет или выбрасывает элементы из списка по какому-то условию, применяемому ко всем элементам поочередно. Функции takeWhile и dropWhile проверяют условие и берут или выкидывают элементы только до первого случая, когда условие не срабатывает.
takeWhile (not.wtspc) "They are coming. They are here" → "They are coming" where
wtspc = `elem` ".,!?"
Или вот еще:
any, all :: (a -> Bool) -> [a] -> Bool
any p = or . map p
all p = and . map p
Надеюсь, для вас уже не составляет труда расшифровать любое из этих определений как:
any p xs = or (map p xs)
all p xs = and (map p xs)
Эти две функции проверяют, выполняется ли определенное условие хотя бы на одном (или, соответственно, на всех) элементе списка:
all (>0) [1,2,3,-5] → False
all (>0) [1,2,3] → True
Обратите внимание, у нас уже одни функции высших порядков (any, all) выражаются через другие (map), и так и должно быть! Всегда, когда вам нужно реализовать какую-то функциональность, постарайтесь увидеть, как эта функциональность реализуется через уже известные вам функции высших порядков, и только потом уже пишите реализацию свою.
($) :: (a -> b) -> a -> b
($) f x = f x
Функция ($), как мы видим, это просто применение функции. Отличается она от обычного применения только приоритетом. Выражение sin x + 1 будет означать (sin x) + 1, а выражение sin $ x + 1 будет вычисляться как sin (x + 1). Таким образом, пробел можно условно рассматривать как бинарный оператор применения функции к аргументу, имеющий наивысший приоритет; тогда как функция ($) имеет, наоборот, наименьший.
zipWith :: (a->b->c) -> [a]->[b]->[c]
zipWith z (a:as) (b:bs) = z a b : zipWith z as bs
zipWith _ _ _ = []
Очень полезная функция zipWith позволяет "слить" вместе два списка, применяя к элементам, стоящим на соответствующих местах, заданную функцию. Например, даже обычная функция zip на самом деле может быть реализована с помощью zipWith (\x y -> (x,y)), а кроме этого:
zipWith (+) [1,1,2,3,5,8] [1,2,3,5,8,13] →
[2,3,5,8,13,21]
fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
Ой, мы случайно написали функцию, находящую бесконечный список чисел Фибоначчи. Да, с Haskell такое бывает…
curry :: ((a,b) -> c) -> (a -> b -> c)
curry f x y = f (x,y)
uncurry :: (a -> b -> c) -> ((a,b) -> c)
uncurry f p = f (fst p) (snd p)
Помните, мы давным-давно говорили о том, что все функции в Haskell принимают ровно один параметр? Помните про каррированные и некаррированные функции? Вот эти две функции позволяют преобразовывать каррированную функцию (принимающую параметры по одному) в некаррированную (принимающую все параметры разом, в виде кортежа) – и наоборот.
span, break :: (a -> Bool) -> [a] -> ([a],[a])
span p [] = ([],[])
span p (x:xs)
| p x = (x:ys, zs)
| otherwise = ([], x:xs)
where (ys,zs) = span p xs
break p = span (not . p)
Функции span и break выполняют сразу работу и takeWhile и dropWhile вместе взятых. Они принимают условие p и список xs, разделяют xs на две части: в первую часть попадает значение takeWhile p xs, а во вторую – dropWhile p xs.
Во второй части этого материала, в задачнике, в главе про списочные функции высших порядков приведены примеры других функций, реально имеющихся в стандартной поставке, и потенциально возможных для реализации. Очень рекомендую со всеми ними ознакомиться, потому что не будет особым преувеличением сказать, что сила функционального программирования заключается именно в широком применении функций высших порядков, совместно с быстрой и удобной подготовкой рабочих функций для них с помощью частичного применения и лямбда-функций.