Почему я должен использовать 'apply' в Clojure?
Это то, что сказал Rich Hickey в одном из сообщений в блоге, но я не понимаю, как мотивация в использовании применяется. Пожалуйста, помогите.
Большая разница между Clojure и CL заключается в том, что Clojure является Lisp -1, поэтому funcall не требуется, а apply применяется только для применения функции к набору аргументов, определенных во время выполнения. Итак, (примените f [i]) можно записать (f i).
Кроме того, что он подразумевает под "Clojure является Lisp -1", а funcall не нужен? Я никогда не программировался в CL.
Спасибо
Ответы
Ответ 1
Вы должны использовать apply, если количество аргументов, передаваемых функции, неизвестно во время компиляции (извините, не знаю синтаксиса Clojure все, что хорошо, прибегая к Схеме ):
(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)))
...))
но это скоро станет кошмаром. То, где применяется, входит в изображение:
(define (call-other-n func . args)
(apply other args))
Он принимает любое количество аргументов, содержащихся в списке, указанном в качестве последнего аргумента, и вызывает функцию, переданную как первый аргумент для применения с этими значениями.
Ответ 2
Термины Lisp -1 и Lisp -2 относятся к тому, находятся ли функции в том же пространстве имен, что и переменные.
В Lisp -2 (т.е. 2 пространства имен) первый элемент в форме будет оцениваться как имя функции - даже если это фактически имя переменной с значением функции. Поэтому, если вы хотите вызвать переменную функцию, вам нужно передать переменную в другую функцию.
В Lisp -1, например Scheme и Clojure, переменные, которые оценивают функции, могут идти в исходной позиции, поэтому вам не нужно использовать apply
для оценки его как функции.
Ответ 3
apply
в основном разворачивает последовательность и применяет к ним функцию как отдельные аргументы.
Вот пример:
(apply + [1 2 3 4 5])
Это возвращает 15. Оно в основном расширяется до (+ 1 2 3 4 5)
вместо (+ [1 2 3 4 5])
.
Ответ 4
Вы используете 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 (или Scheme...) позволяет обозначать только одно, поэтому такие украшения не нужны.
Ответ 5
Обычный шаблон для операций типа приложения заключается в объединении функции, предоставляемой во время выполнения, с набором аргументов, то же самое.
Я не сделал достаточно с clojure, чтобы быть уверенным в тонкостях для этого конкретного языка, чтобы сказать, будет ли необходимость использования приложения в этом случае строго необходима.
Ответ 6
Применить полезно с протоколами, особенно в сочетании с макросами потоковой передачи. Я только что обнаружил это. Поскольку не может использовать макрос и расширять аргументы интерфейса во время компиляции, вы можете применить вместо него вектор с непредсказуемым размером.
Поэтому я использую это, например, как часть интерфейса между записью, содержащей некоторые метаданные о конкретном файле 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-filter и zip к clojure.zip. Макрос xml- > находится из библиотеки clojure.contrib.zip-filter.xml, которая я :use