Функции всегда оценивают свои аргументы перед , вызывая функцию, ведущие выражения должны быть оценены в привычном шаблоне "наизнанку":
(/ (inc 3) (dec 3)) ; orig
(/ 4 2) ; args sent to `/` function
2 ; final result
Цельмакроса , чтобы избежать вычисления аргументов перед вызовом функции, что приводит к тому, что выражения оцениваются "снаружи в".Это позволяет составлять макросы, как если бы каждый из них был новой языковой функцией:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(defmacro infix
[[l op r]]
`(~op ~l ~r))
(defmacro snoop
[& forms]
`(let [result# ~@forms]
(do
(println "snoop => " result#)
result#)))
(dotest
(spyx (infix (2 + 3)))
(spyx (infix (5 - 3)))
(newline)
(snoop (+ 1 2))
(snoop (infix (4 + 5)))
(newline)
(println :last
(infix ((snoop 4) + (snoop 5)))))
с результатом
(infix (2 + 3)) => 5
(infix (5 - 3)) => 2
snoop => 3
snoop => 9
snoop => 4
snoop => 5
:last 9
Так что в последнем примере есть промежуточный этап, который выглядит следующим образом:
(println :last
(+ (snoop 4) (snoop 5)))
, где видно, что infix
был оценен до 2 snoop
вызовов.Это как раз сила макросов, и без них они были бы бесполезны.
Возможно, вы могли бы создать новый макрос, который работал бы так:
(defmacro mmap
[fns coll]
`(let [comp-fn# (comp ~@fns)]
(mapv comp-fn# ~coll)))
(mmap [inc inc inc] [1 2 3]) => [4 5 6]
В то время как выше mmap
работает, это действительно злоупотребление макросистемой и может потерпеть неудачу, если вы дадите ей что-то кроме констант времени компиляции.
Лучшей альтернативой для базовых функций является простое запоминание нормальной функции:
(def do-stuff
(memoize ; will only execute on the first usage
(fn
[coll]
(mapv #(* 2 %) (mapv inc coll)))))
(do-stuff [1 2 3 4]) => [4 6 8 10]
Если вы считаете, что хотя бы один вызов во время выполнения - это слишком много, я бы предложил вручную запустить «макроподобный» код на этапе предварительной компиляции и сохранить результат в новом «исходном файле», который можетзатем скомпилируйте как обычно (возможно, вы предварительно вычисляете кучу простых чисел, например).
В результате система макросов предназначена только для манипулирования кодом с целью добавления новых возможностей языка.Любую другую цель лучше выполнять с использованием другой техники.
Относительно mapv
Я также обнаружил mapv
случайно и только спустя много месяцев.
Обязательно добавьте в закладки Clojure CheatSheet и всегда держите вкладку браузера открытой для него.Регулярно изучайте его, пока не вспомните каждую из функций.:)
Также имейте в виду, что некоторые неясные предметы там не перечислены !
Обновление:
«Убийственная особенность» макросов заключается в добавлении новых языковых возможностей.В Java и др. Можно добавлять только новые библиотеки.В качестве примера, spyx выше - макрос, очень похожий на пример snoop.Для других примеров смотрите with-exception-default, vals-> map и it-> из в библиотеке Tupelo .Фактически, многие «основные» функции Clojure, такие как выражение for или логические операторы and и / или, на самом деле являются макросами, которые любая прикладная программа может написать (или изменить!).