
- •Оглавление
- •От автора
- •Структура
- •Пояснения и обозначения
- •Демонстрация кунг-фу
- •Теория Основные понятия и типы данных
- •Кортежи
- •Функции, операторы
- •Полиморфные типы данных
- •Чтение сигнатур типов
- •Простейшие функции и операторы
- •Арифметические функции
- •Логические функции
- •Списочные функции
- •Кортежные функции
- •Создание своих функций
- •Способ 1. Определение функции как выражения от параметров:
- •Способ 2. Несколько определений одной функции:
- •Способ 3. Определение функции через синоним:
- •Способ 4. Лямбда функция (анонимная функция):
- •Способ 5. Частичное применение функции:
- •Образцы и сопоставление с образцом
- •Синтаксический хлеб и синтаксический сахар
- •Условия и ограничения
- •Локальные определения
- •Двумерный синтаксис
- •Арифметические последовательности
- •Замыкания списков
- •Функциональное мышление
- •Рекурсия как основное средство
- •Ручная редукция выражений
- •Думаем функционально, шаг раз
- •Думаем функционально, шаг два: аккумуляторы
- •Реализация простейших списочных и прочих функций
- •Думаем функционально, шаг три: хвостовая рекурсия
- •Еще раз о рекурсии
- •Полезные хитрости языка
- •Ленивые вычисления и строгие функции
- •Бесконечные списки
- •Функция show
- •Совсем немного о классах
- •Функция read
- •Функция error
- •Побочные эффекты и функция trace
- •Функции высших порядков
- •Мотивация
- •Функция map
- •Функция filter
- •Композиция функций
- •Функция foldr
- •Функция foldl
- •Свертки: разбор полетов
- •Выявление общей функциональности
- •Стандартные функции высших порядков
- •Еще немного про строгие функции
- •Создание своих типов данных
- •Простые перечислимые типы данных
- •Контейнеры
- •О сравнении, отображении и прочих стандартных операциях
- •Параметрические типы данных
- •Сложные типы данных
- •Тип данных Maybe
- •Рекурсивные типы данных: списки
- •Рекурсивные типы данных: деревья
- •Ввод-вывод
- •Простейший ввод-вывод
- •Объяснение кухни
- •Пример программы, производящей нетривиальное преобразование текстового файла
- •Пример решения задачи: Поиск в пространстве состояний
- •Через массивы и последовательность промежуточных состояний
- •Решение для тех, кто не хочет разбираться сам
- •Через списки, лог истории и уникальную очередь
- •Решение для тех, кто не хочет разбираться сам
- •Задачник
- •Пояснения и обозначения
- •Лабораторная работа 1 Простейшие функции
- •Простейшие логические функции
- •Простейшие списочные функции
- •Лабораторная работа 2 Символьные функции
- •Простейшие кортежные функции
- •Теоретико-множественные операции
- •Сортировка
- •Арифметические последовательности
- •Генераторы списков
- •Лабораторная работа 4 Бесконечные списки
- •Ввод-вывод
- •Нетривиальные функции
- •Лабораторная работа 5 Простые числа и факторизация
- •Деревья
- •Деревья вычислений
- •Дополнительные задания для самостоятельной работы Задания с Project Euler
- •Простейший инструментарий Установка WinHugs и начало работы
- •Работа с интерпретатором WinHugs в интерактивном режиме
- •Команды интерпретатору
- •Работа с модулями
- •Список рекомендуемой литературы и электронных ресурсов
Функция foldr
И третий пример, третий набор функций. Потратьте время, разберитесь, что делает каждая из этих функций:
foo [] = 0
foo (x:xs) = x + foo xs
boo [] = 1
boo (x:xs) = x * boo xs
goo [] = True
goo (x:xs) = x && goo xs
moo [] = []
moo (x:xs) = x ++ moo xs
hoo [] acc = acc
hoo (x:xs) acc | x < acc = hoo xs x
| otherwise = hoo xs acc
doo [] acc = acc
doo (x:xs) acc | x > 0 = doo xs (acc+1)
| otherwise = doo xs acc
zoo [] acc = acc
zoo (x:xs) acc = zoo xs (x:acc)
Я специально выбрал аж семь функций для того, чтобы дать вам побольше материала для анализа. Вы, конечно, уже понимаете, что требуется: найти в этих всех функциях общий шаблон поведения, общую абстракцию, общую идею – найти и формализовать в виде еще одной функции высших порядков.
Что делает каждая из этих функций? Первая находит сумму списка (то есть, другими словами, это функция sum), вторая – произведение (product), третья результат логического "и" всего списка (and), четвертая – конкатенация всех подсписков (concat). Пятая функция находит минимальное число из списка (minimum), шестая находит количество положительных элементов, а седьмая просто переворачивает список (reverse).
А что делают все эти функции? Опять-таки, они работают со списками, и обрабатывают элементы списка по одному, пока список не кончится. Первые четыре функции, вроде как, больше похожи друг на друга. Рассмотрим их пока отдельно.
Они "прибавляют" очередной элемент к результату вызова той же функции от хвоста списка, и отличаются смыслом этого "прибавляют" – это, соответственно: бинарное сложение, бинарное умножение, бинарное логическое "и" и бинарная конкатенация списков.
Очевидно, что именно эта отличающаяся операция, назовем ее f, и будет являться параметром новой функции высшего порядка. Вторым отличием первых четырех функций является то, что они возвращают при пустом списке. Это будет второй параметр функции, назовем его – нулевой элемент z.
Если выписать все элементы списка и посмотреть, как к ним применяется эта операция, то окажется, что результат формируется из вычисления вот такого выражения:
x1 f (x2 f (x3 f … (xN f z)
На каждом шаге работы функции бинарная функция f применяется к очередному элементу и результату вызова той же самой функции от хвоста списка. Математики такую операцию называют сверткой (причем правой сверткой). Давайте, наконец, напишем функцию:
foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)
Ура! Как теперь использовать эту функцию для реализации наших семи близнецов?
foo xs = foldr (+) 0 xs
boo xs = foldr (*) 1 xs
goo xs = foldr (&&) True xs
moo xs = foldr (++) [] xs
hoo …
А что же делать с остальными функциями? Какую функцию надо передать в foldr, чтобы получилась функция hoo? Давайте немного перепишем функцию hoo!
hoo [] acc = acc
hoo (x:xs) acc = hoo xs (if x < acc then x else acc)
Вам ничего не напоминает это выражение в скобках? Переименуйте acc в y… Да это же тело бинарной функции min! Может быть, тогда так?
hoo xs = foldr min (head xs) xs
Заметьте, что мы передали в функцию foldr в качестве нулевого элемента z? Ну а что еще – не плюс бесконечность же передавать. Работает! А что с функцией doo? Перепишем:
doo [] acc = acc
doo (x:xs) acc = doo xs (if x > 0 then acc+1 else acc)
И теперь очевидно, что является функцией, передаваемой в foldr:
doo xs = foldr (\x z -> if x > 0 then z+1 else z) 0 xs
Осталась только последняя функция, сейчас мы ее тоже быстренько расколем:
zoo [] acc = acc
zoo (x:xs) acc = zoo xs (x:acc)
Тут даже ничего переписывать не надо, сразу видно, что за операция пойдет в foldr…
zoo xs = foldr (:) [] xs
Проверим… zoo [1,2,3] → [1,2,3]. Вот тебе раз, а где же reverse?