Как я могу получить трассировку стека с хвостовыми вызовами, включенными в Ocaml?

Стек вызовов в ocamldebug - это реальный стек вызовов, поэтому функции, которые сделали хвостовой вызов, не отображаются в нем. Это смущает. Как получить обратную трассировку, которая включает хвостовые вызовы?

Ответы

Ответ 1

Самый простой способ - изменить вашу функцию, чтобы она не была рекурсивной. Это то, что я использую, когда хочу, чтобы хорошие обратные отображения отображались, когда исключение прерывает программу (в этом случае нет необходимости в ocamldebug, достаточно запустить программу под OCAMLRUNPARAM="b"; документация).

Моя личная техника заключается в изменении хвостового вызова на

let result = <tail call> in result

Ocaml в основном компилирует код так, как он написан, и в этом случае это здорово: компилятор не делает этого, и вы получаете красивую обратную линию. Конечно, вы можете легко удалить эту деоптимизацию после обнаружения ошибки. (Это отлично работает, когда у вас всего несколько хвостов, если их много, вы можете обернуть тело всей функции в let result = <body> in result, но я нахожу его немного менее удобным и понятным.)

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

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, чтобы получить трассировку стека по функциям.

Ответ 2

Чтобы увидеть, где находится реальный хвостовой вызов, я могу несколько раз набирать "start", чтобы отменить выполнение и вытащить стек, пока я не доберусь до интересующего пункта назначения вызова, а затем вернусь назад. Трудолюбивый и должен выполняться по принципу "за звонком", но он работает.