Функции более высокого порядка
⇐ ПредыдущаяСтр 6 из 6 Функционалы Функциональный аргумент. Основываясь на едином представлении данных и программ (идущем из ламбда-исчисления), функции в качестве аргумента можно указать и функцию, или, другими словами, определение функции, либо представляющий функцию символ. Аргумент, значением которого является функция, называют в функциональном программировании функциональным аргументом, а функцию, имеющую функциональный аргумент, - функционалом. Различие между понятиями “данные” и “функция” определяется не на основе их структуры, а в зависимости от их использования. Если аргумент используется в функции лишь как объект, участвующий в вычислениях, то мы имеем дело с обыкновенным аргументом, представляющим данные. Если же он используется как средство, определяющее вычисления, т. е. играет в вычислениях, например, роль ламбда-выражения, которое применяется к другим аргументам, то мы имеем дело с функцией. Одно и то же выражение может в связи с различными аспектами выступать, с одной стороны, как обыкновенный аргумент, а с другой стороны, как функциональный. Роль в интерпретации выражения зависит от его синтаксической позиции. Приведем пример:
(first ‘(lambda (x) (list x))) ==> lambda
((lambda (x) (list x)) ‘first) ==> (first)
Сравните с позициями ламбда-выражений в операции аппликации. Далее мы будем использовать понятия функции, вызова функции и значения функции в следующем смысле:
1) функция сама есть изображение вычислений или определение; 2) вызов функции есть применение этого изображения; 3) з начение функции есть результат такого применения.
Функция является функционалом, если в качестве её аргумента используется лисповский объект типа (1), который интерпретируется как функция (2) в теле функционала. Таким функциональным объектом может быть:
· символьное имя, представляющее определение функции (системная функция или функция, определенная пользователем); · безымянное ламбда-выражение; · так называемое замыкание (рассматриваемое позже).
Функциональное значение функции. Функция может быть и результатом функции. Такие функции (или функционалы), говорят, имеют функциональное значение. В Коммон Лиспе предполагается, что в вызове функции на месте имени функции находится символ, определенный с помощью DEFUN как имя функции. Переданный в качестве параметра функциональный объект можно использовать лишь через явный вызов применяющих функционалов (FUNCALL, APPLY). Способы композиции функций
Функции первого порядка:
· обыкновенный вызов (defun f... (g...)...)
· рекурсивный вызов (defun f... (f...)...)
· вложенный рекурсивный вызов (defun f... (f... (f...)...)...)
Функции более высокого порядка:
· функциональный аргумент (defun f (... g...)... (apply g...)...)
· рекурсивный функциональный аргумент (defun f (... g...)...(apply g... g...)...)
Получающий себя в качестве аргумента функционал называют применяемым к самому себе или автоаппликативной (самоприменяемой) функцией. Соответственно функцию, возвращающую саму себя, называют авторепликативной (самовоспроизводящей) функцией. Автоаппликативные и авторепликативные функции образуют класс автофункций. Применяющие функционалы позволяют применять функциональный аргумент к другим параметрам. Применяющие функционалы дают возможность интерпретировать и преобразовывать данные в программу и применять ее в вычислениях. APPLY применяет функцию к списку аргументов (APPLY fn список) <==> (fn ‘x1 ‘ x2... ‘xn)
где список = (x1 x2... xn) Функция fn не должна быть встроенной функцией or или and.
(apply ‘+ ‘(2 3)) ==> 5 (setq f ‘+) (apply f ‘(2 3)) ===> 5 (apply ‘eval ‘(+ 2 3)) ==> 5 (apply ‘apply ‘(+ (2 3))) ==> 5 (apply ‘(lambda (x y) (+ x y)) ‘(2 3)) ==>5
Использование APPLY дает большую гибкость по сравнению с прямым вызовом функции: с помощью одной и той же функции APPLY можно в зависимости от функционального аргумента осуществлять различные вычисления. FUNCALL вызывает функцию с аргументами
Аргументы для вызываемой функции funcall принимает не списком, а по отдельности.
(funcall fn x1 x2... xn) <==> (fn x1 x2... xn) Функция fn не должна быть встроенной функцией or или and.
(funcall ‘list ‘apply ‘‘+ ‘‘(2 3)) ==> (apply (quote +) (quote (2 3)))
(defun do (x f1 f2) (funcall (if (< x 0) f1 f2) x))
(do -1 ’- ’(lambda (x) (* x x x))) ==> 1 (do 4 ’- ’(lambda (x) (* x x x))) ==> 64
Отображающие функционалы повторяют применение функции: отображают список в новый список.
Mapcar - применяет функцию к последовательным элементам списка
Синтаксис: (mapcar <fcn> <list1> <list2>...)
Применяет функцию к последовательным элементам списков: <fcn> - функция или функциональное имя, <listn> - список для каждого аргумента функции.
Возвращает список значений функции.
(mapcar ‘1+ ‘(1 2 3)) ==> (2 3 4) (mapcar ‘(lambda (x) (* 2 (1+ x))) ‘(1 2 3)) ==> (4 6 8) (mapcar ‘+ ‘(1 2 3) ‘(5 6 7)) ==> (6 8 10) (mapcar ‘list ‘(+ + +) ‘(1 2 3) ‘(a b c)) ==> ((+ 1 a) (+ 2 b) (+ 3 c))
(mapcar ’evenp ‘(1 2 3 4)) ==> (nil t nil t)
(mapcar ’equal ‘(1 2 3) ‘(1 4 9 16)) ==> (t nil nil)
(mapcar ’max ‘(1 2 3 4) ‘(1 4 9 16) ‘(11 1 2 10)) ==> (11 4 9 16)
Функционал mapcar с одноместной функцией в качестве функционального аргумента можно определить так:
(defun map (f x) (if (null x) nil (cons (funcall f (first x)) (map f (rest x))) ) )
MAPLIST повторяет вычисления на хвостовых частях списка; действует подобно mapcar, но действия осуществляет не над элементами списка, а над последовательностью хвостов этого списка (начиная с самого исходного списка):
(maplist ‘(lambda (x) x) ‘(a b c)) ==> ((a b c) (b c) (c)) (maplist ‘reverse ‘(a b c)) ==> ((c b a) (c b) (c))
"Универсальный" функционал reduce
(defun reduce (g x a) (if (null x) a (funcall g (first x) (reduce g (rest x) a)) ) )
Пусть x - список, состоящий из элементов (x1 x2 x3 x4... xn), тогда значение функции (reduce g x a) есть результат вычисления выражения
g (x1, g (x2,... g (xn, a)...))
; примеры вызова: ; (reduce '* '(6 4 3) 1) ; (reduce 'max '(6 4 8) 0) ; (reduce 'list '(a b c) nil) => (A (B (C NIL))) ; (reduce ‘(lambda (x y) (list x '+ y)) '(a b c) 'd)
; =>(A + (B + (C + D)))
(defun sumpr (s) (list (reduce ’+ s 0) (reduce ’* s 1)))
Замыкания Чтобы на этапе вызова функционала можно было отличить функциональный аргумент от обычного, функциональный аргумент помечают с помощью предотвращающей вычисления формы FUNCTION: (function функция)
Примеры: (function (lambda (x) (list x y))) (function first)
Форму function называют функциональной блокировкой. Краткое обозначение: #’ f <===> (function f)
Если нужно передать функции данные в том виде, как они записаны, то используется обычная форма QUOTE. Формы QUOTE достаточно и для передачи имени функции или ламбда-выражения, если в нем не используются свободные переменные. В системных функциях свободных переменных нет, поэтому все следующие формы имеют одинаковое значение:
(+ 2 3) (funcall ‘+ 2 3) (funcall (function +) 2 3) (funcall #’+ 2 3) ((lambda (x y) (+ x y)) 2 3) (funcall (function (lambda (x y) (+ x y))) 2 3) Замыкание - это функция и контекст ее определения. Часто бывает полезным и необходимым, чтобы функция для продолжения вычислений могла запомнить связи и состояние более раннего контекста. Это достигается с помощью замыкания - функционального объекта, в котором вместе с самим описанием вычислений сохраняется контекст момента определения функции, защищенный от более позднего контекста вызова. В Коммон Лиспе замыкание создается формой FUNCTION, например, (function (lambda (x) (+ x y))) В замыкание из контекста определения функции включаются лишь связи свободных переменных функции (в приведенном примере глобальное значение y). Если в замыкаемой функции нет свободных переменных, то форма FUNCTION ничем не отличается от формы QUOTE.
Пример: ; результат вызова функции inc - снова функция (defun inc (n) #’(lambda (z) (+ z n))) (defun inc3 (z) (funcall (inc 3) z))
(funcall (inc 4) 5) => 9 Абстрактный подход Абстракция отображения (отображение данных) основана на параметризации ламбда-выражений. Можно с помощью механизма параметризации абстрагировать сами функции, т. е. параметризация касается первого элемента вызова функции - это называется абстракцией вычислений. Обобщение функций, имеющих одинаковый вид. Рассмотрим обобщение с помощью функционалов функций, определения которых с точки зрения абстракции вычислений сходны по строению, но которые довольно различны с точки зрения производимых действий.
Функция последовательного объединения двух списков.
(defun append1 (x y) (if (null x) y (cons (first x) (append1 (rest x) y))))
Функция слияния двух упорядоченных списков в один упорядоченный.
(defun merge (x y) (if (null x) y (insert (first x) (merge (rest x) y))))
(defun insert (a s) (cond ((null s) (list a)) ((< a (first s)) (cons a s)) (t (cons (first s) (insert a (rest s))))) )
Добавим новый функциональный аргумент в reduce.
(defun reduce2 (g f x a) (if (null x) a (funcall g (funcall f (first x)) (reduce2 g f (rest x) a)) ) ) Вместо двух рекурсивных функций append1 и merge применяем обобщающий функционал reduce2
(defun append2 (x y) (reduce2 #’cons #’(lambda (z) z) x y)) (defun merge1 (x y) (reduce2 #’insert #’(lambda (z) z) x y)) Определим функциональный предикат (forall p s), который истинен т. и т.т., когда p(x) истинна для всех элементов списка s.
(defun forall (p s) (reduce #’and (mapcar p s) t))
В этом примере нужно использовать не встроенную функцию and, а определить ее самостоятельно, например, так: (defun and (x y) (if x y nil))
Определим функциональный предикат (forsome p s), который истинен т. и т.т., когда p(x) истинна для некоторого элемента x списка s.
(defun forsome (p s) (reduce #’or (mapcar p s) nil))
В этом примере также нужно использовать не встроенную функцию or, а определить ее самостоятельно, например, так: (defun or (x y) (if x t y))
Функционал reduce2 можно использовать для образных вычислений. (defun member (x s) (reduce2 #’or #’(lambda (y) (equal y x)) s nil))
; число вхождений элемента в список (defun count (x s) (reduce2 #’+ #’ (lambda (y) (if (eql x y) 1 0)) s 0) (defun map (f x) (reduce2 #’cons f x nil)) Абстрагирование вычислений с помощью функций с функциональными значениями. Композицию двух функций можно определить так:
(defun comp (f g) #’(lambda (x) (funcall f (funcall g x))) )
В результате конкретного вызова функции comp возвращается одноместная функция (в Лиспе она трактуется как замыкание). Мы можем теперь строить пирамиды новых функционалов. Например, определим функцию twice: (defun twice (f) (comp f f))
Примеры вызовов:
(funcall (twice #’ (lambda (x) (* x x x))) 2) => 512 (defun inc3 (x) (+ x 3)) (funcall (twice 'inc3) 10) => 16 ( funcall (twice (twice ‘1+)) 1) ==> 5
Определим n-ую степень функции fn <==> f(f(f(f(...f(x)))) (f применяется n раз).
(defun n-comp (f n) (if (zerop n) #’(lambda (x) x) (comp f (n-comp f (1- n)))))
(funcall (n-comp #’1+ 10) 20) ==> 30
Примеры использования функции n-comp:
сложение двух натуральных чисел (defun summa (n m) (funcall (n-comp #’1+ n) m))
(summa 4 9) ==>13
умножение двух натуральных чисел (defun mult (n m) (funcall (n-comp #’(lambda (x) (summa x n)) m) 0))
(mult 5 6) ==> 30
возведение натурального числа в натуральную степень (defun power (n m)
(funcall (n-comp #’(lambda (x) (mult x n)) m) 1))
(power 2 10) ==> 1024
Автофункции В функциональном программировании рассматриваются автоаппликативные и авторепликативные функционалы как функции, которые получают или возвращают в качестве результата самих себя или, точнее, копии самих себя. Конечно, не очевидно, что такие функции вообще существуют. Однако никаких принципиальных препятствий для их существования и определения не существует. В Лиспе их можно определить и использовать довольно просто. Для начала рассмотрим автоаппликативный вариант факториала: (defun factorial (f n) (if (zerop n) 1 (* n (funcall f f (1- n)))))
Для вычисления 10! необходимо функционалу factorial в его вызове в качестве функционального аргумента передать его собственное определение.
> (factorial 'factorial 10) 3628800
Соответственно тому, как мы можем через функциональный аргумент определить автоаппликативную рекурсивную функцию, мы можем определить и возвращающую себя рекурсивно функцию с функциональным значением. Возьмем в качестве примера простейшую возможную функцию, которая возвращает себя в качестве результата. Это лямбда-выражение, примененное к такому же лямбда-выражению с формой QUOTE, т. е. это лямбда-вызов:
((lambda (x) (list x (list ‘quote x))) ‘(lambda (x) (list x (list ‘quote x))))
Форма является одновременно автоаппликативной! Она содержит изображение самой себя, но не целиком, поскольку такое изображение было бы бесконечно длинным. Изображение получается ограниченным благодаря механизму блокировки и лямбда-механизму, а также благодаря автоаппликации. Для проверки присвоим форму переменной self:
(setq self ‘((lambda (x) (list x (list ‘quote x))) ‘(lambda (x) (list x (list ‘quote x)))))
> (eval self) ((lambda (x) (list x (list ‘quote x))) ‘(lambda (x) (list x (list ‘quote x))))
Форму можно вычислять вновь и вновь, и все равно получается тот же результат: само определение функции (его копия). Кроме того, значение функции совпадает со значением аргумента ее вызова. Такое значение называется неподвижной точкой функции. Форма self аналогична известному комбинатору (lx. x x) (lx. x x): b -редукция терма (lx. x x) (lx. x x) приводит к бесконечному циклу (lx. x x) (lx. x x) ® (lx. x x) (lx. x x) ® …. Автоаппликация как форма рекурсии с теоретической точки зрения очень интересна, но в практическом программировании не находит особого применения. С нею связаны теоретические вопросы и проблемы, которые во многом еще не изучены. В формализмах и постановке проблем, которые основаны на похожих на автоаппликацию ссылках на себя (self-reference), очень легко приходят к логическим парадоксам, подобным известному из учебников по теории множеств парадоксу Рассела. Их разрешение предполагает учет типов объектов и кратности ссылок. Однако автоаппликация может открыть новый подход к программированию. Возможными применениями могли бы быть, например, системы, сохраняющие неизменными определенные свойства, хотя какие-то их части изменяются. Такими свойствами могли бы быть наряду с применимостью и репродуцируемостью, например, способность к самоизменениям (self-modification), таким как приспособляемость, согласованность и обучаемость, самосознание и сознательность и т. д. Самосознание предполагает существование модели мира внутри такой системы, включая саму систему в эту модель. Автофункции - это новый класс функций, которые отличаются от обыкновенных рекурсивных функций так же, как рекурсивные функции отличаются от нерекурсивных. Чтобы их можно было использовать в программировании столь же удобно, как и рекурсию, нужно использовать механизмы, которые при программировании учитывают инвариантные свойства вычислений таким же образом, как механизмы определения, вызова и вычисления функций учитывают рекурсию.
[ГАсГ1]
Воспользуйтесь поиском по сайту: ©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...
|