Почему я должен использовать «применить» в Clojure? - PullRequest
43 голосов
/ 11 августа 2009

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

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

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

Спасибо

Ответы [ 6 ]

52 голосов
/ 11 августа 2009

Вы бы использовали 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))

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

40 голосов
/ 11 августа 2009

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

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

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

31 голосов
/ 30 ноября 2009

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

Вот пример:

(apply + [1 2 3 4 5])

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

6 голосов
/ 17 декабря 2013

Вы используете 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])

Cute!

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

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

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

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

2 голосов
/ 11 августа 2009

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

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

0 голосов
/ 24 февраля 2012

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

Так что я использую это, например, как часть интерфейса между записью, содержащей некоторые метаданные о конкретном 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

...