it-roy-ru.com

Почему я должен использовать "применить" в Clojure?

Это то, что Рич Хики сказал в одном из постов в блоге, но я не понимаю мотивацию в применении. Пожалуйста помоги.

Большая разница между Clojure и CL заключается в том, что Clojure - это LISP-1, поэтому funcall не нужен, а apply применяется только для применения функции к коллекции аргументов, определенной во время выполнения. Итак, (применить f [i]) можно записать (f i).

Кроме того, что он имеет в виду под «Clojure - это LISP-1» и funcall не нужен? Я никогда не программировал на CL.

Спасибо

43
unj2

Вы должны использовать apply, если число аргументов, передаваемых в функцию, неизвестно во время компиляции (извините, синтаксис Clojure не слишком хорош, прибегая к Scheme):

(define (call-other-1 func arg) (func arg))
(define (call-other-2 func arg1 arg2) (func arg1 arg2))

Пока число аргументов известно во время компиляции, вы можете передавать их напрямую, как это сделано в примере выше. Но если количество аргументов неизвестно во время компиляции, вы не можете сделать это (ну, вы можете попробовать что-то вроде):

(define (call-other-n func . args)
   (case (length args)
      ((0) (other))
      ((1) (other (car args)))
      ((2) (other (car args) (cadr args)))
      ...))

но это скоро станет кошмаром. Вот где apply входит в картину:

(define (call-other-n func . args)
   (apply other args))

Он принимает любое количество аргументов, содержащихся в списке, заданном в качестве последнего аргумента, и вызывает функцию, переданную в качестве первого аргумента, apply с этими значениями.

52
Dirk

Термины LISP-1 и LISP-2 относятся к тому, находятся ли функции в том же пространстве имен, что и переменные. 

В LISP-2 (то есть в 2 пространствах имен) первый элемент в форме будет оцениваться как имя функции - даже если это фактически имя переменной со значением функции. Поэтому, если вы хотите вызвать переменную функцию, вы должны передать переменную другой функции.

В LISP-1, как Scheme и Clojure, переменные, которые оценивают функции, могут идти в исходное положение, поэтому вам не нужно использовать apply для оценки его как функции.

39
Chuck

apply в основном разворачивает последовательность и применяет функцию к ним в качестве отдельных аргументов.

Вот пример:

(apply + [1 2 3 4 5])

Это возвращает 15. Он в основном расширяется до (+ 1 2 3 4 5) вместо (+ [1 2 3 4 5]).

31
Rayne

Вы используете apply для преобразования функции, которая работает с несколькими аргументами, в функцию, которая работает с одной последовательностью аргументов. Вы также можете вставить аргументы перед последовательностью. Например, map может работать с несколькими последовательностями. Этот пример (из ClojureDocs ) использует map для транспонирования матрицы. 

user=> (apply map vector [[:a :b] [:c :d]])
([:a :c] [:b :d])

Здесь вставлен один аргумент vector. Таким образом, apply расширяется до 

user=> (map vector [:a :b] [:c :d])

Милый! 

PS Чтобы вернуть вектор векторов вместо последовательности векторов, оберните все это в vec

user=> (vec (apply map vector [[:a :b] [:c :d]]))

Пока мы здесь, vec можно определить как (partial apply vector), хотя это не так. 

Относительно LISP-1 и LISP-2: 1 и 2 указывают количество вещей, которые имя может обозначать в данном контексте. В LISP-2 вы можете иметь две разные вещи (функцию и переменную) с одним и тем же именем. Так что, где бы ни было допустимо какое-либо значение, вам нужно украсить вашу программу чем-то, чтобы указать, что вы имеете в виду. К счастью, Clojure (или Схема ...) позволяет имени обозначать только одну вещь, поэтому такие украшения не нужны. 

6
Thumbnail

Обычный шаблон для операций с типом применения - это объединение функции, предоставляемой во время выполнения, с набором аргументов, то же самое. 

Я недостаточно сделал с clojure, чтобы быть уверенным в тонкостях этого конкретного языка, чтобы сказать, будет ли строго необходимо применение apply в этом случае.

2
Steve Gilham

Применить полезно с протоколами, особенно в сочетании с макросами потоков. Я только что обнаружил это. Поскольку вы не можете использовать макрос & для расширения аргументов интерфейса во время компиляции , вы можете вместо этого применить вектор непредсказуемого размера.

Так что я использую это, например, как часть интерфейса между записью, содержащей метаданные о конкретном XML-файле, и самим файлом.

(query-tree [this forms]
  (apply xml-> (text-id-to-tree this) forms)))

text-id-to-tree - это еще один метод этой конкретной записи, который анализирует файл в молнии xml. В другом файле я расширяю протокол определенным запросом, который реализует query-tree, определяя цепочку команд, которые нужно пропустить через макрос xml->:

(tags-with-attrs [this]
  (query-tree this [zf/descendants Zip/node (fn [node] [(map #(% node) [:tag :attrs])])])

(примечание: этот запрос сам по себе вернет много «нулевых» результатов для тегов, которые не имеют атрибутов Фильтруйте и уменьшайте для чистого списка уникальных значений).

zf, кстати, ссылается на clojure.contrib.Zip-фильтр, а Zip на clojure.Zip. Макрос xml-> взят из библиотеки clojure.contrib.Zip-filter.xml, которую я :use

0
tborg