Макрос Apply-recur в Clojure

Я не очень хорошо знаком с макросами Clojure/Lisp. Я хотел бы написать макрос apply-recur, который имел бы такое же значение, как (apply recur ...)

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

Ответы

Ответ 1

Ну, в этом нет необходимости, хотя бы потому, что recur не может принимать varargs (a recur в начало функции принимает один окончательный разделительный аргумент, в котором все аргументы передают последний требуемый аргумент). Разумеется, это не влияет на действительность упражнения.

Однако существует проблема в том, что "правильный" apply-recur должен, по-видимому, обрабатывать аргументы seqs, возвращаемые произвольными выражениями, а не только литералы:

;; this should work...
(apply-recur [1 2 3])

;; ...and this should have the same effect...
(apply-recur (vector 1 2 3))

;; ...as should this, if (foo) returns [1 2 3]
(apply-recur (foo))

Однако значение произвольного выражения, такого как (foo), просто недоступно, в общем, при времени макроразложения. (Возможно, (vector 1 2 3) можно предположить, что всегда дает одно и то же значение, но foo может означать разные вещи в разное время (одна из причин eval не будет работать), быть let -связанным локальным, а не Var (другая причина eval не работает) и т.д.)

Таким образом, чтобы написать полностью общий apply-recur, нам нужно было бы определить, сколько аргументов будет ожидать регулярная форма recur, а (apply-recur some-expression) - на что-то вроде

(let [seval# some-expression]
  (recur (nth seval# 0)
         (nth seval# 1)
         ...
         (nth seval# n-1))) ; n-1 being the number of the final parameter

(Окончательный nth может быть nthnext, если мы имеем дело с varargs, что представляет проблему, аналогичную описанной в следующем абзаце. Также было бы неплохо добавить утверждение для проверки длины разделителя, возвращаемого some-expression.)

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

Таким образом, вывод таков: даже если это вообще возможно написать такой макрос (что может быть даже не так), вполне вероятно, что любая реализация будет очень хрупкой.


В качестве заключительного замечания, написав apply-recur, который мог бы только иметь дело с литералами (на самом деле общая структура arg seq должна была бы быть дана как буквальная, сами аргументы - не обязательно, так что это мог бы работать: (apply-recur [foo bar baz]) = > (recur foo bar baz)) было бы довольно просто. Я не портирую упражнение, отдавая решение, но, как подсказку, рассмотрите возможность использования [email protected].

Ответ 2

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