Clojure Macros: Когда функция не может дублировать поведение макроса? - PullRequest
0 голосов
/ 10 июня 2018

Я играю с макросами clojure и обнаруживаю, что многие макросы можно скопировать с помощью композиции функций.

Хорошим примером этого является макрос потоков:

(defn add1 [n] (+ n 1))
(defn mult10 [n] (* n 10))

(defn threadline [arg]
  (-> arg
      add1
      mult10))

Я могу легко воспроизвести это с помощью функции более высокого порядка, например pipe:

(defn pipe [& fns]
  (reduce (fn [f g] (fn [arg] (g(f arg)))) fns))

(def pipeline
  (pipe
   #(+ % 1)
   #(* % 10)))

Должно бытьслучаи, когда макрос не может быть заменен функцией.Мне было интересно, есть ли у кого-нибудь хорошие примеры подобных ситуаций и повторяющихся тем.

Ответы [ 5 ]

0 голосов
/ 12 июня 2018

На любом языке макросы - функции времени компиляции из кода в код - позволяют вам делать три вещи:

  1. Определить новые формы привязки (например, деструктурирование Clojure let).
  2. Изменить порядок оценки (например, or, and).
  3. Представить предметно-ориентированный язык (например, Instaparse).

Вы можете обсудить 3- действительно ли для реализации DSL требуются макросы.Конечно, вы можете сделать генераторы кода, которые являются функциями из текстовых файлов в текстовые файлы.Или, конечно, вы можете делать DSL в стиле Ruby.Но если вам нужен DSL, интегрированный в компилятор во время компиляции, то макросы - это ваш «API компилятора».

Сказав это, имеет смысл использовать макросы только для этих специальных целей.Максимально используйте функции и / или управляемый данными код.В том числе делать работу за «фасадом», предусмотренным макросом.

0 голосов
/ 11 июня 2018

Макросы не взаимозаменяемы с функциями, но вы можете привести следующие примеры:

(macroexpand '#(+ % 1))
; ==> (fn* [p1__1#] (+ p1__1# 1))

Причина, по которой он работает, заключается в том, что аргумент ожидает функцию, а вы используете макрос, который становится функцией.Однако я знаю, что cond - это макрос.Его нельзя заменить реализацией функции, так как аргументы функции оцениваются, и весь смысл cond состоит в том, чтобы оценивать только некоторые части аргументов в определенном порядке на основе оценки их предикатов.например.создание рекурсивной функции с этим никогда не завершится, поскольку регистр по умолчанию также всегда будет вызываться до того, как будет вычислено тело функции cond.

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

0 голосов
/ 10 июня 2018

Первый макрос, который я когда-либо узнал, является хорошим примером макроса, который нельзя записать в виде простой функции:

(defmacro infix [[arg1 f arg2]]
  (list f arg1 arg2))

(infix (1 + 2))
=> 3

Конечно, этот точный макрос никогда не будет использоваться в дикой природе, ноэто создает основу для более полезных макросов, которые действуют как помощники читабельности.Также следует отметить, что хотя вы можете копировать многие основные макросы с простыми функциями, должно вам?Трудно утверждать, что ваш пример конвейера приводит к более легкому чтению / записи кода, чем, скажем, as->.

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

0 голосов
/ 10 июня 2018

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

Например, вы можете написать версию defn, которая будет называться следующим образом:

(defn 'name '[arg1 arg2]
  '(expression1)
  '(expression2)
  'etc)

Затем выможет eval аргументировать по своему желанию, оценивать их или нет, изменять порядок выполнения или изменять формы перед их оценкой, именно для этого и нужны макросы.

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

Вот так макросы позволяют расширять язык: вам не нужно обрабатывать код макроса иначе, чемобычный код, в отличие, скажем, от JavaScript, Ruby или Python, где язык можно расширить только с помощью новых конструкций потока управления, выполнив то, что вы сделали в своем примере, заключив код в блок, лямбду или функцию.

0 голосов
/ 10 июня 2018

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

Например, and и or Clojure реализованы как рекурсивные макросы, которые расширяются во вложенные формы if.Это позволяет выполнять ленивую оценку внутренних форм and / or, т. Е. Если первая форма or верна, ее значение будет возвращено, а остальные не будут оценены.Если вы написали and / or как функцию, все ее аргументы будут оценены до того, как их можно будет изучить.

Короткое замыкание потока управления не является проблемой в вашем примере функции pipe,но pipe добавляет значительную сложность во время выполнения по сравнению с ->, который просто разворачивается во вложенные формы.Более интересным макросом, который можно реализовать в виде функции, может быть some->.

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

Если ваши функции поддаются ему, вы, безусловно, можете заменить простой макрос многопоточности на композицию функций на comp, аналогично стилю «без точек» в других функциональных языках: #(-> % inc str) функционально эквивалентен (comp str inc) и #(str (inc %)).

Как правило, рекомендуется по возможности отдавать предпочтение функциям, и даже при написании макроса вы обычно можете использовать большую часть "работы" для функций (функций).

...