Составьте пример в ANSI Common Lisp Пола Грэма - PullRequest
13 голосов
/ 08 мая 2011

Кто-нибудь может объяснить пример в ANSI Common Lisp на странице 110 Пола Грэма?

В примере попытайтесь объяснить использование & rest и lambda для создания функциональных средств программирования. Одним из них является функция для составления функциональных аргументов. Я не могу найти ничего, объясняющего, как это работает. Код выглядит следующим образом:

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

Ответы [ 3 ]

9 голосов
/ 08 мая 2011

Функция compose возвращает замыкание , которое вызывает каждую из функций от последней к первой, передавая результат каждого вызова функции следующему.

Закрытие, возникающее в результатеВызов (compose #'list #'round #'sqrt) сначала вычисляет квадратный корень его аргумента, округляет результат до ближайшего целого числа, а затем создает список результата.Вызов замыкания с допустимым аргументом 3 эквивалентен оценке (list (round (sqrt 3))).

destructuring-bind оценивает выражение (reverse fns), чтобы получить аргументы compose в обратном порядке, и связывает свой первый элемент результирующего списка с fn1 локальная переменная и остальная часть результирующего списка в локальную переменную rest .Следовательно, fn1 содержит последний элемент fns , #'sqrt.

. redu * вызывает каждую функцию 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)))
7 голосов
/ 08 мая 2011

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)
5 голосов
/ 08 мая 2011

Хорошо, вот так:

  1. Он принимает данные функции, переворачивает их (в вашем примере это становится (#'sqrt #'round #'list)), затем вставляет первый элемент в fn1, а остальныев rest.У нас есть: fn1 = #'sqrt и rest = (#'round #'list).
  2. Затем выполняется сгиб, используя (apply sqrt args) (где args - значения, полученные для результирующей лямбды)в качестве начального значения, и с каждой итерацией захватывает следующую функцию из rest для вызова.
    1. Для первой итерации вы заканчиваете (round (apply sqrt args)), а для второй итерации вы заканчиваете (list (round (apply sqrt args))).
  3. Интересно, что только начальная функция (sqrt в вашем случае) разрешено принимать несколько аргументов.Остальные функции вызываются только с одним аргументом, даже если какая-то конкретная функция в цепочке возвращает несколько значений.
...