1 / 30

Функциональное программирование

Функциональное программирование. Лекция 2 (13) Функционалы и их разновидности. Содержание. Функционалы общие определения виды и примеры Лямбда-выражения Особые виды функционалов композиции фильтры редукции. Функционалы. Определение, примеры. Понятие отображения.

mort
Download Presentation

Функциональное программирование

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Функциональное программирование Лекция 2 (13) Функционалы и их разновидности

  2. Содержание • Функционалы • общие определения • виды и примеры • Лямбда-выражения • Особые виды функционалов • композиции • фильтры • редукции

  3. Функционалы Определение, примеры

  4. Понятие отображения • Отображения - эффективный механизм абстрагирования, моделирования, проектирования и формализации крупномасштабной обработки информации - за счет ее разбиения ее обработки на части разного назначения. Это позволяет упрощать отладку систем определений, повышать коэффициент повторного использования отлаженных функций. • Говорят, что отображение существует, если задана пара множеств и отображающая функция, для которой первое множество - область определения, а второе - область значения. • При определении отображений, прежде всего, д.б. ясны следующие вопросы: • что представляет собой отображающая функция; • как организовано данное, представляющее отображаемое множество; • каким способом выделяются элементы отображаемого множества, передаваемые в качестве аргументов отображающей функции. • Это позволяет задать порядок перебора множества и метод передачи аргументов для вычисления отображающей функции. При обходе структуры, представляющей множество, отображающая функция будет применена к каждому элементу множества. • Проще всего выработать структуру множества результатов, подобную исходной структуре. Но возможно не все полученные результаты нужны или требуется собрать их в иную структуру, поэтому целесообразно прояснить заранее еще ряд вопросов: • где размещается множество полученных результатов; • чем отличаются нужные результаты от полученных попутно; • как строится итоговое данное из отобранных результатов.

  5. Понятие функционала • При функциональном подходе ответ на каждый из описанных выше вопросов может быть дан в виде отдельной функции, причем роль каждой функции в схеме реализации отображения четко фиксирована. • Схема реализации отображения может быть представлена в виде определения, формальными параметрами которого являются обозначения функций, выполняющих эти роли. Такое определение называется "функционал". • Функционалы - это функции, которые используют в качестве аргументов или результатов другие функции. • При построении функционалов переменные могут играть роль имен функций, определения которых находятся во внешних формулах, использующих функционалы. • Аргумент, значением которого является функция, называют функциональным аргументом, а функцию, имеющую функциональный аргумент - функционалом. • Различие между понятиями "данные" и " функция", определяются не на основе их структуры, а в зависимости от использования: • Если аргумент используется в функции, как объект участвующий в вычислениях, то это данные. • Если аргумент используется как средство, определяющее вычисления, то это функция.

  6. Пример: обычные похожие функции • Построить список из <голов> элементов списка (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)

  7. Сводим обе функции к одной – функционалу • Внешние отличия в записи этих трех функций малосущественны, что позволяет ввести более общую функцию 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)) ; Длины элементов • Эти определения функций формально эквивалентны ранее приведенным - они сохраняют отношение между аргументами и результатами. Параметром функционала может быть любая вспомогательная функция.

  8. Покомпонентная обработка: пример обработки двух списков • Задача: Построить ассоциативный список, т.е. список пар из имен и соответствующих им значений, по заданным спискам имен и их значений: (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 . три))

  9. Функционал для покомпонентной обработки • Теперь попробуем определить по аналогии универсальную функцию покомпонентной обработки двух списков с помощью заданной функции 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)

  10. Пример применения функционала 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)) • Если списки разной длины, то длина результата будет равна длине наименьшего.

  11. Виды функционалов Отображающие, применяющие…

  12. Применяющие функционалы • Одним из видов функционалов, используемых в Лиспе являются применяющие функционалы. Они применяют функциональный аргумент к его параметрам. • Так как применяющие функционалы вычисляют значение функции, в этом смысле они аналогичны функции EVAL, вычисляющей значение выражения. • Общий формат: (<имя_функционала><применяемая_функция><аргумент>)

  13. Функционал 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)

  14. Примеры применения 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

  15. Функционал 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

  16. Пример использования 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))

  17. Отображающие функционалы • Важный класс функционалов используемых в лиспе - отображающие функционалы (МАР функционалы)- функции, которые некоторым образом отображают (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)

  18. Отображающие функционалы • Отображающий функционал можно написать самим, а можно и воспользоваться одним из встроенных. Согласно стандарту, в базовую реализацию языка Лисп обычно включены функционалы: 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)) 

  19. Отображающие функционалы • 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)

  20. Лямбда-выражения Безымянные функции

  21. Зачем нужны лямбда-выражения, безымянные функции • Определения в описанных примерах не вполне удобны по следующим причинам: • в определениях целевых функций встречаются имена специально определенных вспомогательных функций; • формально эти функции независимы, значит, программист должен отвечать за их наличие при использовании целевых функций на протяжении всего жизненного цикла программы, что трудно гарантировать; • вероятно, имя вспомогательной функции будет использоваться только один раз - в определении целевой функции. • С одной стороны, последнее утверждение противоречит пониманию смысла именования как техники, обеспечивающей неоднократность применения поименованного объекта. С другой стороны, придумывать подходящие, долго сохраняющие понятность и соответствие цели, имена - задача нетривиальная. • Учитывая это, было бы удобнее вспомогательные определения вкладывать непосредственно в определения целевых функций и обходиться при этом вообще без имен. Конструктор функций LAMBDA обеспечивает такой стиль построения определений. Этот конструктор любое выражение EXPR превращает в функцию с заданным списком аргументов (X1. .. XK) в форме так называемых LAMBDA-выражений: (LAMBDA (x1 ... xK) expr) • Имени такая функций не имеет, поэтому м.б. применена лишь непосредственно. DEFUN использует данный конструктор, но требует дать функциям имена. • Любую систему взаимосвязанных функций можно преобразовать к одной функции, используя вызовы безымянных функций.

  22. Лямбда-выражения • Структура МАР функций ограничивает формы отображаемых функций. Так, если мы желаем получить список с элементами 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 (параметры) <тело функции>) Рассмотрим подробнее…

  23. Пример использования лямбда-выражений • Пусть дана вспомогательная функция 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)) • Но является ли это решение самым компактным, и эффективным?

  24. Используем лямбда-выражение • Ниже приведено определение функции 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) )

  25. Композиции функционалов, фильтры и редукции Лишние сложности или упрощение вычислений?

  26. Композиции функционалов • Вызовы функционалов можно объединять в более сложные структуры таким же образом, как и вызовы обычных функций, а их композиции можно использовать в определениях новых функций. • Композиции функционалов позволяют создавать и более мощные построения, достаточно ясные, но требующие некоторого внимания. • С их помощью, можно применять серии функций к списку общих аргументов или к параллельно заданной последовательности списков их аргументов. • и серии, и последовательности представляются списками • Пример: Для заданного списка вычислим ряд его атрибутов, а именно - длина, первый элемент, остальные элементы списка без первого. (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))

  27. Фильтры • Фильтр отличается от обычного отображения тем, что окончательно собирает не все результаты, а лишь удовлетворяющие заданному предикату. • Пример: Построить список голов непустых списков: ; временно голова размещается в список, ; чтобы потом списки сцепить (defun heads (xl) (map-ap `(lambda (x) (cond (x (cons (car x) NIL) ) ) ) xl ) ) > (heads `((1 2) () (3 4) () (5 6)) ) (1 3 5)

  28. Редукция • Если нас интересуют некие интегральные характеристики результатов, полученных при отображении, например, сумма полученных чисел, наименьшее или наибольшее из них и т.п., то можно говорить о свертке результата или его редукции. • Редукция заключается в сведении множества элементов к одному элементу, в вычислении которого задействованы все элементы множества. • Пример: Подсчитать сумму элементов заданного списка. (*) Перестроим такое определение, чтобы вместо <+> можно было использовать любую бинарную функцию: (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

  29. Выводы по применимости функционалов В общем случае, отображающие функционалы представляют собой различные виды структурной итерации или итерации по структуре данных. При решении сложных задач полезно использовать отображения и их композиции, а также иметь в виду возможность создания своих функционалов. Показанные построения достаточно разнообразны, чтобы можно было сформулировать, в чем преимущества применения функционалов: • отображающие функционалы позволяют строить программы из крупных действий; • функционалы обеспечивают гибкость отображений; • определение функции может совсем не зависеть от конкретных имен; • с помощью функционалов можно управлять выбором формы результатов; • параметром функционала может быть любая функция, преобразующая элементы структуры; • функционалы позволяют формировать серии функций от общих данных; • встроенные в Лисп базовые функционалы приспособлены к покомпонентной обработке произвольного числа параметров; • любую систему взаимосвязанных функций можно преобразовать к одной функции, используя вызовы безымянных функций.

  30. Литература • Л.В. Городняя. Основы функционального программирования – ИНТУИТ [ http://www.intuit.ru/department/pl/funcpl/] • Морозов М.Н. Функциональное программирование. Курс лекций - 1999-2001 г. [ http://www.mari-el.ru/mmlab/home/lisp/title.htm ]

More Related