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

В чем разница между defn и defmacro?

В чем разница между defn и defmacro? В чем разница между функцией и макросом?

4b9b3361

Ответ 1

defn определяет функцию, defmacro определяет макрос.

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

Макросы, с другой стороны, описывают преобразование из одного фрагмента кода в другое. Любая оценка происходит после преобразования.

Это означает, что аргументы могут оцениваться несколько раз или вообще не выполняться. В качестве примера or используется макрос. Если первый аргумент or является ложным, второй аргумент никогда не будет оценен. Если or была функцией, это было бы невозможно, потому что аргументы всегда были бы оценены до запуска функции.

Другим следствием этого является то, что аргументы макроса не должны быть допустимым выражением перед расширением макроса. Например, вы можете определить макрос mymacro, чтобы (mymacro (12 23 +)) расширялся до (+ 23 12), поэтому это будет работать, даже если (12 23 +) сам по себе будет бессмыслен. Вы не могли бы сделать это с помощью функции, потому что (12 23 +) будет оцениваться и вызывать ошибку перед запуском функции.

Небольшой пример, иллюстрирующий разницу:

(defmacro twice [e] `(do ~e ~e))
(twice (println "foo"))

Макрос twice получает список (println "foo") в качестве аргумента. Затем он преобразует его в список (do (println "foo") (println "foo")). Этот новый код запускается.

(defn twice [e] `(do ~e ~e))
(twice (println "foo"))

Здесь println "foo" сразу же оценивается. Поскольку println возвращает nil, дважды вызывается с nil в качестве аргумента. twice теперь создает список (do nil nil) и возвращает его в качестве результата. Обратите внимание, что здесь (do nil nil) не оценивается как код, он просто рассматривается как список.

Ответ 2

В других ответах подробно рассказывается об этом, поэтому я постараюсь, чтобы это было сжато, насколько я могу. Я был бы признателен за редактирование/комментарии о том, как писать его более кратко, сохраняя при этом ясность:

  • a Функция преобразует значения в другие значения.
    (reduce + (map inc [1 2 3])) => 9

  • a макрос преобразует код в другой код.
    (-> x a b c) => (c (b (a x))))

Ответ 3

Макрос похож на программиста-ученика, на который вы можете писать заметки:

Иногда, если я пытаюсь что-то отлаживать, мне нравится что-то менять, например

(* 3 2)

В чем-то вроде этого:

(let [a (* 3 2)] (println "dbg: (* 3 2) = " a) a)

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

Это может быть очень полезно, но это требует много времени и ошибок. Вы можете себе представить делегирование таких задач вашему ученику!

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

;;debugging parts of expressions
(defmacro dbg[x] `(let [x# ~x] (println "dbg:" '~x "=" x#) x#))

Теперь попробуйте:

(* 4 (dbg (* 3 2)))

Фактически это делает текстовое преобразование кода для вас, хотя будучи компьютером, он выбирает нечитаемые имена для своих переменных вместо "a", которые я бы выбрал.

Мы можем спросить его, что он будет делать для данного выражения:

(macroexpand '(dbg (* 3 2)))

И это его ответ, поэтому вы можете видеть, что он действительно переписывает код для вас:

(let* [x__1698__auto__ (* 3 2)]
      (clojure.core/println "dbg:" (quote (* 3 2)) "=" x__1698__auto__)
      x__1698__auto__)

Попробуйте написать функцию dbgf, которая делает то же самое, и у вас будут проблемы, потому что (dbgf (* 3 2)) → (dbgf 6) до вызова dbgf, и поэтому, что бы ни делал dbgf, он может 't восстановить выражение, которое нужно распечатать.

Я уверен, что вы можете придумать много способов обойти это, например, оценку времени выполнения или передачу строки. Попробуйте написать dbg, используя defn вместо defmacro. Это будет хороший способ убедить себя, что макросы - это хорошие вещи на языке. Как только вы его заработаете, попробуйте использовать его в выражении, которое имеет побочный эффект, а также значение, например

(dbg (print "hi"))

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

C также имеет макросы, которые работают примерно одинаково, но они всегда идут не так, и чтобы их правильно исправить, вам нужно добавить столько скобок в свою программу, что это выглядит как LISP!

На самом деле вам не рекомендуется использовать макросы C, потому что они настолько подвержены ошибкам, хотя я видел, что они очень сильно влияли на людей, которые действительно знали, что они делают.

Макросы

LISP настолько эффективны, что сам язык построен из них, как вы заметите, если вы посмотрите на исходные файлы Clojure, которые сами записаны в Clojure.

Базовый язык очень прост, поэтому его легко реализовать, а затем сложная надстройка построена с использованием макросов.

Надеюсь, это поможет. Это довольно долго, чем мои обычные ответы, потому что вы задали глубокий вопрос. Удачи.

Ответ 4

Без звучания snarky создается функция, а другая создается макросом. В Common Lisp (и я предполагаю, что это относится и к clojure), макросы расширяются до фактической компиляции функций. Итак, ленивый кот:

(defmacro lazy-cat
  "Expands to code which yields a lazy sequence of the concatenation
  of the supplied colls. Each coll expr is not evaluated until it is
  needed.

  (lazy-cat xs ys zs) === (concat (lazy-seq xs) (lazy-seq ys) (lazy-seq zs))"
  {:added "1.0"}
  [& colls]
  `(concat [email protected](map #(list `lazy-seq %) colls)))

Будет фактически расширен до

`(concat [email protected](map #(list `lazy-seq %) colls)))

из которых lazy-seq будет далее расширен до

(list 'new 'clojure.lang.LazySeq (list* '^{:once true} fn* [] body)))

до фактической обработки переданных им данных.

Там очень милая история, которая помогает объяснить разницу (за которой следуют некоторые информативные примеры) в Практическая общая Lisp: Глава 8

Ответ 5

defn определить функцию, а defmacro - определить макрос.
Макрос как функция, но обрабатывает его аргумент (если они являются выражениями как данные), затем обрабатывать их и возвращать данные (список символов, которые являются кодом), а затем оценивать этот код возврата. Поэтому он заменяет один код другим (во время компиляции).