Каковы некоторые хорошие способы реализации исключения хвостовых вызовов?
Я написал небольшой интерпретатор Scheme в безобразной смеси C/C++, но мне еще предстоит реализовать правильные хвостовые вызовы.
Я знаю о классическом Чейни о алгоритме MTA, но есть ли другие хорошие способы реализации этого? Я знаю, что мог бы поместить стек Scheme в кучу, но это все равно не будет правильным исключением, так как стандарт говорит, что нужно поддерживать неограниченное количество активных оконечных вызовов.
Я также возился с longjmps, но до сих пор думаю, что это будет хорошо работать только для невзаимных рекурсивных хвостовых вызовов.
Как основные схемы на основе C реализуют правильную хвостовую рекурсию?
3 ответа
Проще, чем написать компилятор и виртуальную машину, - зарегистрировать и перехватить ваш интерпретатор. Поскольку у вас есть интерпретатор, а не компилятор (я полагаю), вам потребуется всего пара простых преобразований, чтобы получить надлежащую поддержку хвостовых вызовов.
Сначала вам придется написать все в стиле продолжения, что может быть странно думать и делать на C/C++. Учебное пособие Дэна Фридмана по ParentheC поможет вам преобразовать высокоуровневую рекурсивную программу в форму, которая может быть переведена машиной на C.
В конце концов, вы по существу реализуете простую виртуальную машину, где вместо обычных вызовов функций для выполнения eval, applyProc и т. Д. Вы передаете аргументы, устанавливая глобальные переменные, а затем выполняете goto
к следующему аргументу (или используйте цикл верхнего уровня и программный счетчик)...
return applyProc(rator, rand)
становится
reg_rator = rator
reg_rand = rand
reg_pc = applyProc
return
То есть все ваши функции, которые обычно вызывают друг друга рекурсивно, сводятся к псевдосборке, в которой они являются просто блоками кода, которые не повторяются. Цикл верхнего уровня управляет программой:
for(;;) {
switch(reg_pc) {
case EVAL:
eval();
break;
case APPLY_PROC:
applyProc();
break;
...
}
}
Редактировать: я прошел тот же процесс для моего хобби интерпретатора Scheme, написанного на JavaScript. Я воспользовался многими анонимными процедурами, но это может помочь в качестве конкретной ссылки. Посмотрите историю коммитов FoxScheme, начиная с 2011-03-13 (30707a0432563ce1632a) и заканчивая 2011-03-15 (5dd3b521dac582507086).
Редактировать ^2: нехвостовая рекурсия будет по-прежнему использовать память, даже если она не находится в стеке.
Не зная, что у вас есть, я бы сказал, что самый простой (и самый поучительный) способ сделать это - реализовать компилятор схемы и виртуальную машину из Dybvig "Три модели реализации для схемы".
Я сделал это здесь, в Javascript (там же есть копия PDF-файла Дибвига): https://github.com/z5h/zb-lisp
проверьте src/compiler.js: compileCons и реализацию "кодов операций" в src/vm.js
Если вы заинтересованы в методах реализации интерпретаторов, нет пути обойти книгу Кристиана Квиннека "LiSP - Лисп в маленьких частях". Это объясняет все аспекты реализации системы Scheme очень тщательно с полным кодом. Это прекрасная книга.
http://www.amazon.com/exec/obidos/ASIN/0521562473/qid=945541473/sr=1-2/002-2995245-1849825
Но не забудьте проверить документы на ReadScheme.org.
Секция
Технология компиляции / Методы реализации и оптимизации http://library.readscheme.org/page8.html
Есть довольно много работ по оптимизации хвостового вызова.
Среди прочего вы найдете ссылку на тезис Дибвига (классический), который очень хорошо написан. Это объясняет и мотивирует все очень четко.