Подтвердить что ты не робот

Составить пример в 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 особенно напоминают мне волшебство. Любые комментарии будут оценены.

4b9b3361

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