Да, на самом деле вы можете взять некоторый код и преобразовать каждый вызов функции и каждый возврат в хвостовой вызов. То, что у вас получается, называется стилем продолжения передачи или CPS.
Например, вот функция, содержащая два рекурсивных вызова:
(define (count-tree t)
(if (pair? t)
(+ (count-tree (car t)) (count-tree (cdr t)))
1))
А вот как бы это выглядело, если бы вы преобразовали эту функцию в стиль передачи продолжения:
(define (count-tree-cps t ctn)
(if (pair? t)
(count-tree-cps (car t)
(lambda (L) (count-tree-cps (cdr t)
(lambda (R) (ctn (+ L R))))))
(ctn 1)))
Дополнительный аргумент ctn
- это процедура, которая count-tree-cps
вызывает вместо возврата. (ответ sdcvvc говорит, что вы не можете делать все в пространстве O (1), и это правильно; здесь каждое продолжение - это закрытие, которое занимает некоторую память.)
Я не преобразовывал звонки на car
, cdr
или +
в хвостовые звонки. Это тоже можно сделать, но я предполагаю, что эти листовые вызовы действительно будут встроенными.
Теперь самое интересное. Chicken Scheme фактически выполняет это преобразование во всем компилируемом коде. Процедуры, составленные Chicken, никогда не возвращаются. Есть классическая статья, объясняющая, почему Chicken Scheme делает это, написанная в 1994 году до внедрения Chicken: МИНУСЫ не должны Минусы его аргументов, Часть II: Чейни о MTA
Как ни странно, стиль передачи продолжения довольно распространен в JavaScript. Вы можете использовать его для длительных вычислений, избегая всплывающего окна браузера с «медленным сценарием». И это привлекательно для асинхронных API. jQuery.get
(простая оболочка вокруг XMLHttpRequest) явно в стиле продолжения; последний аргумент - это функция.
person
Jason Orendorff
schedule
11.12.2009