Составить пример в Paul Graham ANSI Common Lisp
Может ли кто-нибудь объяснить пример в Paul Graham ANSI Common Lisp стр. 110?
В примере попытайтесь объяснить использование & rest и лямбда для создания возможностей функционального программирования. Одна из них - это функция для составления функциональных аргументов. Я не могу найти ничего объясняющего, как это работает. Код выглядит следующим образом:
(defun compose (&rest fns)
(destructuring-bind (fn1 . rest) (reverse fns)
#'(lambda (&rest args)
(reduce #'(lambda (v f) (funcall f v))
rest
:initial-value (apply fn1 args)))))
Использование:
(mapcar (compose #'list #'round #'sqrt)
'(4 9 16 25))
Вывод:
((2) (3) (4) (5))
Линии 2 и 6 особенно напоминают мне волшебство. Любые комментарии будут оценены.
Ответы
Ответ 1
Функция compose
возвращает closure, которая вызывает каждую из функций от последнего до первого, передавая результат каждой функции вызовите следующий.
Закрытие, вызванное вызовом (compose #'list #'round #'sqrt)
, сначала вычисляет квадратный корень из его аргумента, округляет результат до ближайшего целого числа, а затем создает список результатов. Вызов замыкания с утверждением 3 в качестве аргумента эквивалентен оценке (list (round (sqrt 3)))
.
destructuring-bind оценивает выражение (reverse fns)
для получения аргументов compose
в обратном порядке и связывает свой первый элемент результирующий список к локальной переменной fn1, а остальная часть результирующего списка - к остальной локальной переменной. Следовательно, fn1 содержит последний элемент fns, #'sqrt
.
reduce вызывает каждую функцию fns
с накопленным результатом. :initial-value (apply fn1 args)
предоставляет начальное значение функции reduce
и поддерживает вызов замыкания с несколькими аргументами. Без требования нескольких аргументов compose
можно упростить до:
(defun compose (&rest fns)
#'(lambda (arg)
(reduce #'(lambda (v f) (funcall f v))
(reverse fns)
:initial-value arg)))
Ответ 2
destructuring-bind
сочетает деструкторы со связыванием. Деструктор - это функция, которая позволяет вам получить доступ к части структуры данных. car
и cdr
- простые деструкторы для извлечения головы и хвоста списка. getf
- общая структура деструктора. Связывание чаще всего выполняется let
. В этом примере fns
есть (#'list #'round #'sqrt)
(аргументы compose
), поэтому (reverse fns)
- (#'sqrt #'round #'list)
. Тогда
(destructuring-bind (fn1 . rest) '(#'sqrt #'round #'list)
...)
эквивалентно
(let ((tmp '(#'sqrt #'round #'list)))
(let ((fn1 (car tmp))
(rest (cdr tmp)))
...))
за исключением того, что он не связывает tmp
, конечно. Идея destructuring-bind
заключается в том, что она построена по шаблону: ее первым аргументом является шаблон, который должны соответствовать данные, а символы в шаблоне привязаны к соответствующим частям данных.
Итак, теперь fn1
есть #'sqrt
, а rest
- (#'round #'list)
. Функция compose
возвращает функцию: (lambda (&rest args) ...)
. Теперь рассмотрим, что происходит, когда вы применяете эту функцию к некоторому аргументу, например 4
. Лямбда может быть применена, что дает
(reduce #'(lambda (v f) (funcall f v))
'(#'round #'list)
:initial-value (apply #'sqrt 4)))
Функция apply
применяет fn1
к аргументу; поскольку этот аргумент не является списком, это просто (#'sqrt 4)
, который равен 2
. Другими словами, мы имеем
(reduce #'(lambda (v f) (funcall f v))
'(#'round #'list)
:initial-value 2)
Теперь функция reduce
выполняет свою работу, которая должна последовательно применять #'(lambda (v f) (funcall f v))
к #'round
и #'list
, начиная с 2
. Это эквивалентно
(funcall #'list (funcall #'round 2))
→ (#'list (#'round 2))
→ '(2)
Ответ 3
Хорошо, здесь идет:
- Он принимает заданные функции, меняет его (в вашем примере он становится
(#'sqrt #'round #'list)
), затем вставляет первый элемент в fn1
, а остальное - в rest
. Мы имеем: fn1
= #'sqrt
и rest
= (#'round #'list)
.
- Затем он выполняет сгиб, используя
(apply sqrt args)
(где args
- значения, данные полученному lambda) в качестве начального значения, и с каждой итерацией захватывает следующую функцию из rest
для вызова.
- Для первой итерации вы получите
(round (apply sqrt args))
, а вторую итерацию - (list (round (apply sqrt args)))
.
- Интересно, что только начальная функция (
sqrt
в вашем случае) допускает несколько аргументов. Остальные функции вызываются только с одиночными аргументами, даже если какая-либо конкретная функция в цепочке возвращает несколько значений.