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

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

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

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

Кроме того, что он подразумевает под "Clojure является Lisp -1", а funcall не нужен? Я никогда не программировался в CL.

Спасибо

4b9b3361

Ответ 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