Применить-повторить макрос в Clojure - PullRequest
14 голосов
/ 08 сентября 2010

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

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

Ответы [ 2 ]

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

Ну, на самом деле в этом нет необходимости, хотя бы потому, что 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)) было бы довольно просто. Я не портю упражнение, выдавая решение, но в качестве подсказки рассмотрите возможность использования ~@.

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

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

...