300 likes | 574 Views
Функциональное программирование. Лекция 2 (13) Функционалы и их разновидности. Содержание. Функционалы общие определения виды и примеры Лямбда-выражения Особые виды функционалов композиции фильтры редукции. Функционалы. Определение, примеры. Понятие отображения.
E N D
Функциональное программирование Лекция 2 (13) Функционалы и их разновидности
Содержание • Функционалы • общие определения • виды и примеры • Лямбда-выражения • Особые виды функционалов • композиции • фильтры • редукции
Функционалы Определение, примеры
Понятие отображения • Отображения - эффективный механизм абстрагирования, моделирования, проектирования и формализации крупномасштабной обработки информации - за счет ее разбиения ее обработки на части разного назначения. Это позволяет упрощать отладку систем определений, повышать коэффициент повторного использования отлаженных функций. • Говорят, что отображение существует, если задана пара множеств и отображающая функция, для которой первое множество - область определения, а второе - область значения. • При определении отображений, прежде всего, д.б. ясны следующие вопросы: • что представляет собой отображающая функция; • как организовано данное, представляющее отображаемое множество; • каким способом выделяются элементы отображаемого множества, передаваемые в качестве аргументов отображающей функции. • Это позволяет задать порядок перебора множества и метод передачи аргументов для вычисления отображающей функции. При обходе структуры, представляющей множество, отображающая функция будет применена к каждому элементу множества. • Проще всего выработать структуру множества результатов, подобную исходной структуре. Но возможно не все полученные результаты нужны или требуется собрать их в иную структуру, поэтому целесообразно прояснить заранее еще ряд вопросов: • где размещается множество полученных результатов; • чем отличаются нужные результаты от полученных попутно; • как строится итоговое данное из отобранных результатов.
Понятие функционала • При функциональном подходе ответ на каждый из описанных выше вопросов может быть дан в виде отдельной функции, причем роль каждой функции в схеме реализации отображения четко фиксирована. • Схема реализации отображения может быть представлена в виде определения, формальными параметрами которого являются обозначения функций, выполняющих эти роли. Такое определение называется "функционал". • Функционалы - это функции, которые используют в качестве аргументов или результатов другие функции. • При построении функционалов переменные могут играть роль имен функций, определения которых находятся во внешних формулах, использующих функционалы. • Аргумент, значением которого является функция, называют функциональным аргументом, а функцию, имеющую функциональный аргумент - функционалом. • Различие между понятиями "данные" и " функция", определяются не на основе их структуры, а в зависимости от использования: • Если аргумент используется в функции, как объект участвующий в вычислениях, то это данные. • Если аргумент используется как средство, определяющее вычисления, то это функция.
Пример: обычные похожие функции • Построить список из <голов> элементов списка (defun 1st (xl) (cond ; "головы" элементов = CAR пока список не пуст (xl (cons (caar xl); выбираем CAR от его головы (1st (cdr xl)) ; и переходим к остальным, ) ) ) ) ; собирая результаты в список > (1st`((один два)(one two)(1 2)) ) (один one 1) • Пример: Выяснить длины элементов списка (defun lens (xl) ; Длины элементов (cond ; Пока список не пуст (xl (cons (length (car xl)) ; вычисляем длину его головы (lens (cdr xl)); и переходим к остальным, ) ) ) ) ; собирая результаты в список > (lens `((1 2)()(a b c d)(1(a b c d)3))) (2 0 4 3)
Сводим обе функции к одной – функционалу • Внешние отличия в записи этих трех функций малосущественны, что позволяет ввести более общую функцию mapcar, в определении которой имена <car> и <length> могут быть заданы как значения параметра fn: (defun mapcar(fn xl) (cond ; Поэлементное преобразование XL с помощью функции FN (xl (cons(funcall fn (car xl)) ; применяем FN как функцию голове XL (mapcar fn (cdr xl)) ) ) ) ) ; и переходим к остальным, собирая результаты в список • Эффект функций 1st и lens можно получить выражениями: (mapcar `car xl) ; "головы" элементов = CAR (mapcar `length xl) ; Длины элементов > (mapcar `car `((один два)(one two)(1 2))) (один one 1) > (mapcar `length `((1 2)()(a b c d)(1(a b c d)3)) ) (2 0 4 3) • Оба примера можно решить с помощью таких определяющих выражений: (defun 1st(xl) (mapcar `car xl)) ; "головы" элементов = CAR (defun lens(xl) (mapcar `length xl)) ; Длины элементов • Эти определения функций формально эквивалентны ранее приведенным - они сохраняют отношение между аргументами и результатами. Параметром функционала может быть любая вспомогательная функция.
Покомпонентная обработка: пример обработки двух списков • Задача: Построить ассоциативный список, т.е. список пар из имен и соответствующих им значений, по заданным спискам имен и их значений: (DEFUN pairl (al vl) ; Ассоциативный список (COND ; Пока al не пуст, (al (CONS (CONS (CAR al) (CAR vl)) ; пары из <голов>. (pairl (CDR al) (CDR vl)) ; Если vl исчерпается, ; то CDR будет давать NIL ) ) ) ) > (pair '(один два two three) '(1 2 два три)) ((один . 1)(два . 2)(two . два)(three . три))
Функционал для покомпонентной обработки • Теперь попробуем определить по аналогии универсальную функцию покомпонентной обработки двух списков с помощью заданной функции FN: (DEFUN map-comp (fn al vl) ; fn покомпонентно применитьк al и vl (COND (al (CONS (FUNCALL fn (CAR al) (CAR vl)) ; Вызов данного fn как функции (map-comp (CDR al) (CDR vl)) ) ) ) ) • Теперь покомпонентные действия над векторами, представленными с помощью списков, полностью в наших руках. Достаточно уяснить, что надо делать с элементами списка, остальное довершит функционал MAP-COMP, отображающий этот список в список результатов заданной функции. Вот списки и сумм, и произведений, и пар, и результатов проверки на совпадение: >(map-comp #'+'(1 2 3) '(4 6 9)) ; Суммы (5 8 12) > (map-comp #'*'(1 2 3) '(4 6 9)) ; Произведения (4 12 27) >(map-comp #'CONS'(1 2 3) '(4 6 9)) ; Пары ((1 . 4) (2 . 6) (3 . 9)) >(map-comp #'EQ'(4 2 3) '(4 6 9)) ; Сравнения (T NIL NIL)
Пример применения функционала MAPCAR к нескольким аргументам • MAPCAR может обрабатывать больше списков, если в функциональном аргументе несколько аргументов. (defun addlist (l1 l2) (mapcar '+ l1 L2)) > (addlist '( 1 2 3) '(1 2 3)) (2 4 6) ~ (list (+ 1 1) (+ 2 2) (+ 3 3)) • Если списки разной длины, то длина результата будет равна длине наименьшего.
Виды функционалов Отображающие, применяющие…
Применяющие функционалы • Одним из видов функционалов, используемых в Лиспе являются применяющие функционалы. Они применяют функциональный аргумент к его параметрам. • Так как применяющие функционалы вычисляют значение функции, в этом смысле они аналогичны функции EVAL, вычисляющей значение выражения. • Общий формат: (<имя_функционала><применяемая_функция><аргумент>)
Функционал APPLY • Данный функционал применяет заданную функцию к аргументам - элементам списка, формируя общий результат (родственен редукции, рассм. далее) • Пример : Предположим мы хотим объединить в один список несколько вложенных списков, т.е. из ((a b c) (d e f) (k l)) получить (a b c d e f k l) • Для этой задачи используем функцию apply, которая имеет два аргумента: имя функции и список, и применяет названную функцию к элементам списка, как к аргументам функции: > (apply 'append '((a)(b)(c))) (A B C)
Примеры применения APPLY • Apply можно использовать в комбинации с другими операциями (в т.ч. как аргумент). Пример: Определим функцию, которая рассчитывает среднее списка чисел (defun list-mean (x) (/ (apply `+ x) (length x))) > (list-mean `(1 2 3 4)) 2.5 • Часто apply используют вместе c марсаr. Пример: найти общее число элемент в списках (defun countall (lis) (apply `+ (mapcar ` length lis)) ) > (countall `((a b c) (d e f) (k l))) 8 • Можно определить более сложную функцию countatom, которая считает элементы-атомы в любом списке. (defun countatom (lis) (cond ((null lis) 0) ((atom lis) 1) (t (apply `+ (mapcar `countatom lis))))) > (countatom `(a (a (b) c) (d) e (f g))) 8
Функционал FUNCALL • Применяющий функционал FUNCALL аналогичен APPLY, но аргументы он принимает, не в списке, а по отдельности: (funcall fn x1 x2 ... xN) ~ (fn x1 x2 ... xN) • здесь fn - функция с n aргументами. > (funcall '+ 1 2) 3 > (+ 1 2) 3 > (funcall (car '(+ - / *)) 1 2) 3
Пример использования funcall Рассмотрим использование funcallдля построения функции map2, которая действует аналогично mapcar, но берет в качестве аргументов два элемента из списка, а не один. Эта функция имеет вид: (defun map2 (f2 lst) (cond ((null lst) nil) (t (cons (funcall f2 (car lst) (cadrlst)) ; выполняем конкатенацию результаты вызова ф-ии ; f2 для головы и 2го элемента (map2 f2 (cddrlst))) ; с результатом рекурсивного вызова исходной ф-ии ; для «укороченного» хвоста ) ) ) >(map2 'list '(A Christie V Nabokov K Vonnegut)) ((A Christie) (V Nabokov) (K Vonnegut))
Отображающие функционалы • Важный класс функционалов используемых в лиспе - отображающие функционалы (МАР функционалы)- функции, которые некоторым образом отображают (map) заданный список в новый список. MAPCAR один из основных отображающих функционалов. Он имеет два аргумента: 1) функция, 2) список: (MAPCAR f '(x1 x2 x3 ... xN)) • Когда MAPCAR выполняется , функция определенная первым аргументом применяется к каждому элементу, списка, определенному вторым аргументом и результат помещает (отображает) в новый список. > (mapcar 'add1 '( 1 2 3)) (2 3 4) • (MAPCAR f '(x1 x2 x3 ... xN))эквивалентнозаписи: (list (f 'x1) (f 'x2) .... (f 'xN)) • Можно использовать в функциях (defun list-add1 (lis) (mapcar 'add1 lis)) > (list-add1 '(1 2 3)) (2 3 4) • В качестве аргумента для MAPCAR может быть использовано значение символа > (setq x '(a b (d))) (setq y 'atom) > (mapcar y x) (t t nil)
Отображающие функционалы • Отображающий функционал можно написать самим, а можно и воспользоваться одним из встроенных. Согласно стандарту, в базовую реализацию языка Лисп обычно включены функционалы: map, mapcar, maplist, mapcan, mapcon, mapc, mapl. Каждый из них покомпонентно обработает любой набор списков. Отличаются они схемами выбора аргументов для отображающей функции, характером воздействия на исходные данные и оформлением результатов, передаваемых объемлющим формулам. Рассмотрим их по отдельности, с примерами вызова. • Map(map result-type function sequences ... ) Функция function вызывается на всех первых элементах последовательностей, затем на всех вторых и т.д. Из полученных результатов function формируется результирующая последовательность, строение которой задается параметром result-type с допустимыми значениями cons, list, array, string, NIL. > (map `list `+ `(1 2 3) `(4 5 6)) (5 7 9) • Mapcar( mapcar function list ... ) Функция function применяется к первым элементам списков, затем ко вторым и т.д. Другими словами, function применяется к <головам> методично сокращающихся списков, и результаты применения собираются в результирующий список. > (mapcar `list `(1 2 3) `(4 5 6)) ((1 4)(2 5)(3 6))
Отображающие функционалы • Maplist ( maplist function list ... ) Функционал аналогичен mapcar, но function применяется к <<хвостам>> списков list, начиная с полного списка. > (maplist `list `(1 2 3) `(4 5 6)) (((1 2 3) (4 5 6)) ((2 3) (5 6)) ((3) (6))) • Mapc и Mapl Оба функционала работают как mapcar и maplist, соответственно, за исключением того, что они в качестве формального результата выдают первый список (своеобразная аналогия с формальными аргументами). > (mapc `list `(1 2 3) `(4 5 6)) > (mapl `list `(1 2 3) `(4 5 6)) (1 2 3) (1 2 3) • Mapcan и Mapcon эти два функционала аналогичны mapcar и maplist, но формирование результатов происходит не с помощью операции cons, которая строит данные в новых блоках памяти, а с помощью деструктивной функции nconc, которая при построении новых данных использует память исходных данных, из-за чего исходные данные могут быть искажены. > (mapcan `list `(1 2 3 4)) > (mapcon `list `(1 2 3 4)) (1 2 3 4) ((1 2 3 4) (2 3 4) (3 4) (4)) • Map-into Функционал отображает результат в конкретную последовательность. > (setq a (list 1 2 3 4) b (list 10 10 10 10)) > (map-into a `+ a b) (10 10 10 10) (11 12 13 14)
Лямбда-выражения Безымянные функции
Зачем нужны лямбда-выражения, безымянные функции • Определения в описанных примерах не вполне удобны по следующим причинам: • в определениях целевых функций встречаются имена специально определенных вспомогательных функций; • формально эти функции независимы, значит, программист должен отвечать за их наличие при использовании целевых функций на протяжении всего жизненного цикла программы, что трудно гарантировать; • вероятно, имя вспомогательной функции будет использоваться только один раз - в определении целевой функции. • С одной стороны, последнее утверждение противоречит пониманию смысла именования как техники, обеспечивающей неоднократность применения поименованного объекта. С другой стороны, придумывать подходящие, долго сохраняющие понятность и соответствие цели, имена - задача нетривиальная. • Учитывая это, было бы удобнее вспомогательные определения вкладывать непосредственно в определения целевых функций и обходиться при этом вообще без имен. Конструктор функций LAMBDA обеспечивает такой стиль построения определений. Этот конструктор любое выражение EXPR превращает в функцию с заданным списком аргументов (X1. .. XK) в форме так называемых LAMBDA-выражений: (LAMBDA (x1 ... xK) expr) • Имени такая функций не имеет, поэтому м.б. применена лишь непосредственно. DEFUN использует данный конструктор, но требует дать функциям имена. • Любую систему взаимосвязанных функций можно преобразовать к одной функции, используя вызовы безымянных функций.
Лямбда-выражения • Структура МАР функций ограничивает формы отображаемых функций. Так, если мы желаем получить список с элементами x * x + 1, илиx + 1 мы должны определить функции (defun f1 (x) (+ 1 (* xx))) > (mapcar 'f1 '(1 2 3)) (2 5 10) (defunf2 (x) (+ 1 x)) > (mapcar 'f2 '(1 2 3)) (2 3 4) • Таким образом определяется специальная функция, которая используется только в MAPCAR.Более эффективно в этом случае использовать, т.н. лямбда выражения: > (mapcar '(lambda (x) (+ 1 (* xx))) '(1 2 3)) > (mapcar '(lambda (x) (+ 1 x)) '(1 2 3)) • Т.о. лямбда выражения позволяют определять функцию внутри другой функции. (Лямбда-выражения определяют функцию не имеющую имени) • Общая формазаписи: (lambda (параметры) <тело функции>) Рассмотрим подробнее…
Пример использования лямбда-выражений • Пусть дана вспомогательная функция sqw, возводящая числа в квадрат (defun sqw (x)(* x x)) ; Возведение числа в квадрат > (sqw 3) 9 • Построить список квадратов чисел, используя функцию sqw: (defun sqware (xl) ; Возведение списка чисел в квадрат (cond ; Пока аргумент не пуст, (xl (cons (sqw (car xl)) ; применяем sqw к его голове (sqware(cdr xl)) ; и переходим к остальным, ) ) ) ) ; собирая результаты в список (sqware`(1 2 5 7)) ; (1 4 25 49 ) • Можно использовать mapcar: (defun sqware (xl) (mapcar `sqw xl)) • Но является ли это решение самым компактным, и эффективным?
Используем лямбда-выражение • Ниже приведено определение функции sqware без вспомогательной функции, выполняющее умножение непосредственно. Оно влечет за собой двойное вычисление (CAR xl), т.е. такая техника не вполне эффективна: (defun sqware (xl) (cond (xl (cons (* (car xl) (car xl) ) (sqware (cdr xl)) ) ) ) ) • Определение функции sqware – вычисляющей квадраты элементов числового списка, без использования имен и вспомогательных функций (оцените краткость!): (defun sqware (xl) (mapcar `(lambda (x) (* x x)) xl) )
Композиции функционалов, фильтры и редукции Лишние сложности или упрощение вычислений?
Композиции функционалов • Вызовы функционалов можно объединять в более сложные структуры таким же образом, как и вызовы обычных функций, а их композиции можно использовать в определениях новых функций. • Композиции функционалов позволяют создавать и более мощные построения, достаточно ясные, но требующие некоторого внимания. • С их помощью, можно применять серии функций к списку общих аргументов или к параллельно заданной последовательности списков их аргументов. • и серии, и последовательности представляются списками • Пример: Для заданного списка вычислим ряд его атрибутов, а именно - длина, первый элемент, остальные элементы списка без первого. (defun mapf (fl el) (cond ; Пока первый аргумент не пуст, (fl (cons (funcall (car fl) el) ; применяем очередную функцию ; ко второму аргументу (mapf (cdr fl) el) ; и переходим к остальным функциям, ) ) ) ) ; собирая их результаты в общий список >(mapf `(length car cdr) `(a b c d)) 4 a (b c d))
Фильтры • Фильтр отличается от обычного отображения тем, что окончательно собирает не все результаты, а лишь удовлетворяющие заданному предикату. • Пример: Построить список голов непустых списков: ; временно голова размещается в список, ; чтобы потом списки сцепить (defun heads (xl) (map-ap `(lambda (x) (cond (x (cons (car x) NIL) ) ) ) xl ) ) > (heads `((1 2) () (3 4) () (5 6)) ) (1 3 5)
Редукция • Если нас интересуют некие интегральные характеристики результатов, полученных при отображении, например, сумма полученных чисел, наименьшее или наибольшее из них и т.п., то можно говорить о свертке результата или его редукции. • Редукция заключается в сведении множества элементов к одному элементу, в вычислении которого задействованы все элементы множества. • Пример: Подсчитать сумму элементов заданного списка. (*) Перестроим такое определение, чтобы вместо <+> можно было использовать любую бинарную функцию: (defun red-el (fn xl) (cond ((null xl) 0 ) (xl (funcall fn (car xl) (red-el fn (cdr xl)) ))) ) > (red-el `+ `(1 2 3 4) ) 10 (defun sum-el (xl) (cond ((null xl) 0 ) (xl (+ (car xl) (sum-el (cdr xl) ) ))) ) >(sum-el `(1 2 3 4) ) 10
Выводы по применимости функционалов В общем случае, отображающие функционалы представляют собой различные виды структурной итерации или итерации по структуре данных. При решении сложных задач полезно использовать отображения и их композиции, а также иметь в виду возможность создания своих функционалов. Показанные построения достаточно разнообразны, чтобы можно было сформулировать, в чем преимущества применения функционалов: • отображающие функционалы позволяют строить программы из крупных действий; • функционалы обеспечивают гибкость отображений; • определение функции может совсем не зависеть от конкретных имен; • с помощью функционалов можно управлять выбором формы результатов; • параметром функционала может быть любая функция, преобразующая элементы структуры; • функционалы позволяют формировать серии функций от общих данных; • встроенные в Лисп базовые функционалы приспособлены к покомпонентной обработке произвольного числа параметров; • любую систему взаимосвязанных функций можно преобразовать к одной функции, используя вызовы безымянных функций.
Литература • Л.В. Городняя. Основы функционального программирования – ИНТУИТ [ http://www.intuit.ru/department/pl/funcpl/] • Морозов М.Н. Функциональное программирование. Курс лекций - 1999-2001 г. [ http://www.mari-el.ru/mmlab/home/lisp/title.htm ]