Подтвердить что ты не робот

Почему нельзя оптимизировать хвостовые звонки в Lisp на основе JVM?

Основной вопрос: я рассматриваю наиболее значительное применение оптимизации хвостовых вызовов (TCO) как перевод рекурсивного вызова в цикл (в случаях, когда рекурсивный вызов имеет определенную форму). Точнее, при переводе на машинный язык это обычно означает перевод в какую-то серию скачков. Некоторые компиляторы Common Lisp и Scheme, которые компилируют собственный код (например, SBCL), могут идентифицировать хвостовой рекурсивный код и выполнять этот перевод. JVM-based Lisps, такие как Clojure и ABCL, не могут этого сделать. Что такое JVM как машина, которая мешает или затрудняет это? Я не понимаю. Очевидно, что JVM не имеет проблем с циклами. Это компилятор, который должен выяснить, как сделать TCO, а не машину, с которой она компилируется.

Связанный с этим вопрос: Clojure может преобразовать, по-видимому, рекурсивный код в цикл: он действует так, как будто он выполняет TCO, если программист заменяет хвостовой вызов функции ключевым словом recur. Но если можно получить компилятор для идентификации хвостовых вызовов - например, как SBCL и CCL, то почему компилятор Clojure не может понять, что он должен рассматривать хвостовой вызов так, как он относится к recur?

(Извините - это, несомненно, FAQ, и я уверен, что вышеприведенные замечания показывают мое невежество, но я не смог найти более ранние вопросы.)

4b9b3361

Ответ 1

Real TCO работает для произвольных вызовов в позиции хвоста, а не только для собственных вызовов, поэтому код, подобный приведенному ниже, не вызывает переполнение стека:

(letfn [(e? [x] (or (zero? x) (o? (dec x))))
        (o? [x] (e? (dec x)))]
  (e? 10))

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

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

Ответ 2

Существует причина, по которой JVM не поддерживает TCO: Почему JVM по-прежнему не поддерживает оптимизацию хвостового вызова?

Однако есть способ обойти это, злоупотребляя памятью кучи, и некоторые обманы, объясненные в Документ преобразования CPS Transpass первого порядка; он реализован в Clojure Крисом Фризом и Даниэлем П. Фридманом (см. clojure-tco).

Теперь Rich Hickey, возможно, решил сделать такую ​​оптимизацию по умолчанию, Scala делает это в некоторых точках. Вместо этого он решил полагаться на конечного пользователя, чтобы указать случаи, когда они могут быть оптимизированы с помощью Clojure с конструкциями trampoline или loop-recur. Решение было объяснено здесь: https://groups.google.com/d/msg/clojure/4bSdsbperNE/tXdcmbiv4g0J

Ответ 3

В заключительной презентации ClojureConj 2014 Брайан Гетц указал, что в JVM есть функция безопасности, которая предотвращает свертывание фрейма стека (так как это будет вектор атаки для людей, которые хотят, чтобы функция возвращалась в другое место).

https://www.youtube.com/watch?v=2y5Pv4yN0b0&index=21&list=PLZdCLR02grLoc322bYirANEso3mmzvCiI