Как я могу получить трассировку стека с хвостовыми вызовами, включенными в Ocaml?
Стек вызовов в ocamldebug является реальным стеком вызовов, поэтому функции, которые сделали хвостовой вызов, не отображаются в нем. Это смущает. Как я могу получить обратную трассировку, которая включает хвостовые вызовы?
2 ответа
Самый простой способ - изменить свою функцию, чтобы она больше не была хвостовой рекурсивной. Это то, что я использую, когда я хочу отображать хорошие следы, когда исключение прерывает программу (в этом случае нет необходимости в ocamldebug, когда программа запускается под OCAMLRUNPARAM="b"
достаточно; документация).
Моя личная техника состоит в том, чтобы изменить хвостовой вызов
let result = <tail call> in result
Ocaml в основном компилирует код в том виде, в котором он написан, и в этом случае это здорово: компилятор этого не встроит, и вы получите приятный внешний вид. Конечно, вы можете легко удалить эту деоптимизацию, как только ошибка найдена.
(Это прекрасно работает, когда у вас есть только несколько оконечных вызовов; если у вас их много, вы можете заключить все тело функции в let result = <body> in result
, но я нахожу это немного менее удобным и понятным.)
Если вам нужно, чтобы функция все еще была вызовом taill (например, у вас есть предельный размер стека, установленный в ОС, который вы можете исчерпать), вы можете преобразовать стек вызовов для этой функции в структуру данных, превратив
let rec f arg1 arg2 .. argN =
...
f arg1' arg2' .. argN'
в
let rec f stack arg1 arg2 .. argN =
let stack' = (arg1,arg2,..,argN)::stack in
...
f stack' arg1' arg2' .. argN'
Затем вы можете, в ocamldebug, изучить значение stack
переменная, чтобы получить функциональную трассировку стека.
Чтобы увидеть, где находится настоящий хвостовой вызов, я могу многократно набирать "start", чтобы отменить выполнение, и вытолкнуть стек, пока не доберусь до интересующего пункта назначения вызова, а затем выполнить шаг назад. Трудоемко, и должно быть сделано по вызову, но это работает.