Ну, на самом деле в этом нет необходимости, хотя бы потому, что recur
не может принимать переменные (recur
в верхней части функции принимает один последний аргумент seqable, группирующий все аргументы, передающие последний требуемый аргумент). Конечно, это не влияет на обоснованность упражнения.
Однако существует проблема в том, что «правильный» 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
.)
Мне неизвестен какой-либо метод определения правильной арности recur
в конкретном месте кода во время макроразложения. Это не означает, что один недоступен - это то, что компилятор должен знать в любом случае, так что, возможно, есть способ извлечь эту информацию из его внутренних компонентов. Тем не менее, любой метод для этого почти наверняка должен опираться на детали реализации, которые могут измениться в будущем.
Таким образом, вывод таков: даже если вообще возможно написать такой макрос (что может быть даже не так), вполне вероятно, что любая реализация будет очень хрупкой.
В качестве последнего замечания, написание apply-recur
, который мог бы работать только с литералами (на самом деле общую структуру arg seq нужно было бы задавать как литерал; сами аргументы - необязательно, так что мог бы работать: (apply-recur [foo bar baz])
=> (recur foo bar baz)
) было бы довольно просто. Я не портю упражнение, выдавая решение, но в качестве подсказки рассмотрите возможность использования ~@
.