Clojure - как работает макроразложение внутри функции some - PullRequest
2 голосов
/ 26 июня 2019

Как раз тогда, когда я подумал, что у меня довольно хорошая работа с макросами, я наткнулся на источник для some, который на первый взгляд показался мне немного странным.

(defn some
  [pred coll]
    (when (seq coll)
      (or (pred (first coll)) (recur pred (next coll)))))

Моим первым инстинктом было то, чтоПохоже, что это будет потреблять стек, но потом я вспомнил: «Нет, фиктивный, or - это макрос, поэтому он просто расширится до тонны вложенных ifs».

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

(defn some
  [pred coll]
    (when (seq coll)
      (let [or__4469__auto__  (pred (first coll))]
         (if or__4469__auto__ 
             or__4469__auto__ 
             (recur pred (next coll))))))

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

Нет второго макроразложения, нет вложенных блоков if, только один блок if.Вызов recur просто продолжает повторное связывание pred и coll, но один и тот же блок, приведенный выше, продолжает проверять правду до тех пор, пока не найдет его или коллекция не закончится и не будет возвращено nil.

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

Ответы [ 2 ]

3 голосов
/ 26 июня 2019

Для начала обратите внимание, что любая функция может служить неявным loop выражением. Кроме того, recur работает так же, как рекурсивный вызов функции, за исключением того, что он не использует стек из-за уловки компилятора (поэтому loop & recur являются «специальными формами» - они не следуют правилам нормальных функций).

Также помните, что when - это макрос, который расширяется до выражения if.

Сказав все это, вы пришли к правильному выводу.

0 голосов
/ 26 июня 2019

Здесь существует два режима рекурсии:

  • Макрос or неявно рекурсивен, спровоцирован последовательностью форм аргументов для генерации дерева if форм.
  • Функция some явно рекурсивна, провоцирует на указание единственной последовательности своего последнего аргумента.Тот факт, что эта рекурсия recur способна, не имеет значения.

Каждый аргумент макроса or за пределами первого создает вложенную форму if.Например, ...

=> (clojure.walk/macroexpand-all '(or a b c)) 
(let* [or__5501__auto__ a]
  (if or__5501__auto__ or__5501__auto__
    (let* [or__5501__auto__ b]
      (if or__5501__auto__ or__5501__auto__ c))))

У вас есть два аргумента для or, поэтому одна if форма.* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *] * * * * * * * * * * * * * * * * ”* * * * * * * * * * * * * * * * * * *” * ”*” Разворачивается ”when.

Вы можете иметь столько вложенных форм if, сколько пожелаете, листья дерева if, все они находятся в хвостовой позиции.Следовательно, все немедленные рекурсивные вызовы могут recur.Если бы такой хвостовой рекурсии не было, вызов recur не скомпилировался бы.

...