В чем разница между defn и defmacro? - PullRequest
32 голосов
/ 08 сентября 2010

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

Ответы [ 5 ]

57 голосов
/ 08 сентября 2010

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

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

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

Это означает, что аргументы могут оцениваться несколько раз или не выполняться вообще. В качестве примера or это макрос. Если первый аргумент or равен false, второй аргумент никогда не будет оцениваться. Если бы 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) не оценивается как код, а только как список.

36 голосов
/ 09 сентября 2010

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

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

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

11 голосов
/ 08 сентября 2010

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

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

(* 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, она не может восстановитьсявыражение, которое нужно распечатать.

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

(dbg (print "hi"))

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

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

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

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

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

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

3 голосов
/ 08 сентября 2010

Без звучного саркастического, один создает функцию , а другой создает макрос . В 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 ~@(map #(list `lazy-seq %) colls)))

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

`(concat ~@(map #(list `lazy-seq %) colls)))

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

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

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

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

0 голосов
/ 08 сентября 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...