СЕМИНАР42сентябрь 2011 / ИНФОРМАТИКАматематической задачи. Условие задачи ставитсятак: необходимо найти все такие прямо угольныетреугольники со стороной не больше 10 и периметром,равным 24. В императивных языках эта задачаимеет не самое тривиальное и не самое очевидноерешение, связанное с неоднократным применениемциклов. В Haskell же все ясно: необходимоиспользовать списочное выражение. Представимтреугольник в виде кортежа из значений длин сторон— (a, b, c). Перечислим все прямоугольныетреугольники со сторонами не больше 10:Hugs> [(a, b, c) | c Intlength' [] = 0length' (_:xs) = 1 + length' xsСпециальная точка в области определенияфункции длины — пустой список — выделена вособый случай, длина такого списка равна нулю.Длина же любого непустого списка равна единице,плюс длина его хвоста. Голова в данном случаенам не нужна, поэтому обозначена подчеркиванием.
Выражение x:xs, два частных случая которого —x:_ и _:xs часто применяется при определении черезсравнение с образцом функций, работающихсо списками. Здесь x является головой списка иимеет тот же тип, что и все элементы списка, например,a, а xs является хвостом списка и имееттип “список элементов типа a”. В отдельных случаяхприменяются и более сложные выражения, например,x:y:xs или даже x:y:z:xs.Следующим важным моментом в определениифункций являются охранные выражения. Подобнаяформа записи должна быть знакома многим, ведькаждый, кто изучал в школе математику, хоть разда определял кусочно заданные функции с ее помощью.Разумеется, это та самая крайне удобная возможностьразбить область определения сложнойфункции на несколько подобластей и определитьфункцию отдельно для каждой из этих под областей.Простейший пример — функция, ищущая максимальноеиз двух целых чисел:max' :: Int -> Intmax' a b| a > b = a| b >= a = bКак видите, для разбиения области определенияфункции используется вертикальная черта, послекоторой следует неравенство, показывающее отношениемежду входными данными, подобных разбиенийможет быть любое количество. В вышеописанномопределении есть недочет: оно избыточно,содержит лишнее неравенство. Ведь если a не большеb, то это автоматически обозначает, что b большеили равно a. Для описания случаев, которые неописываются ни одним из встреченных ранее неравенств,применяется ключевое слово otherwise, сним это определение станет куда элегантнее:max' :: Int -> Intmax' a b| a > b = a| otherwise = bВ мои задачи не входит описать все возможныеварианты определения функций, поэтому, рассмотревлишь самые интересные из них, отсутствующиев привычных нам императивных языках программирования,я закончу речь о подобных формальностяхи перейду к краткому описанию действительномощного метода программирования: использованиюфункций высших порядков, что являетсяисключительной прерогативой и основой мощифункционального стиля.Функции от функций от функцийОдна из фундаментальных концепций функциональногопрограммирования пришла из теориилямбда-исчисления, своебразного формализма, подобногомашине Тьюринга, созданного для того,чтобы конструктивно описывать математическиевыражения. Эта концепция носит имя лямбдавыраженийи, иными словами, представляет собойанонимные функции, то есть функции, не имеющиеимени.Для того чтобы понять, зачем они нужны, необходимосказать, что функции в Haskell являютсясобственным полноправным типом данных:они могут быть аргументами и результатамидругих функций. В Haskell такая терминологияне распространена, но обычно функции, принимающиеили возвращающие другие функции,называют функционалами, а в среде любителейHaskell — функциями высших порядков. В теориифункционального программирования выделяютдва самых важных функционала: map и filter.Функционал map принимает функцию f и список,и применяет эту функцию к каждому элементусписка, формируя новый список:Hugs> map sin [1, 2, 3, 4][0.84<strong>14</strong>70984807897,0.909297426825682,0.<strong>14</strong>1120008059867,-0.756802495307928]Функционал filter принимает функцию, котораявозвращает логическое значение (предикат) исписок, а возвращает список тех элементов исходногосписка, для которых применение предикатадает результат True.Hugs> filter (> 2) [1, 2, 3, 4][3,4]В этом примере мы отсеиваем из списка всезначения, которые меньше или равны двойке.Заметьте интересную запись функции — (> 2),этот прием называется каррингом, в честь математикаХаскелла Карри. В языке Haskell вы можетелюбой функции дать неполный набор аргументови получить при этом новую функцию, котораяпримет оставшиеся аргументы. Яркий пример использованиятакой особенности показан выше:применение таких неполных функций в функционалахmap и filter.Точно такого же эффекта, как применение карринга,но с большей гибкостью, можно достичь спомощью анонимных функций, то есть лямбдавыражений.Перепишем пример для filter с их использованием:Hugs> filter (\x -> x > 2) [1, 2, 3, 4][3,4]Как видите, анонимная функция определяетсяследующим образом: \ аргумент1 ... аргументN –>тело (символ “\” обозначает греческую букву “лямбда”).Лямбда-выражение может иметь любое числоаргументов и в принципе является полноправнойфункцией, а не урезанной версией существующей,что дает нам большую мощь при их использовании.Один из классических примеров использованияанонимных функций: обобщенная версия функциисортировки, принимающая список, подлежащийсортировке, и функцию, которая сравнивает по некоторымправилам два аргумента.На этом многообразие применения функцийвысших порядков не заканчивается, но в мою за-43сентябрь 2011 / ИНФОРМАТИКА