Ответ 1
К сожалению, нет тривиального способа.
Не так сложно читать исходный код и использовать типы и определять, является ли что-то хвостовым вызовом путем проверки (это "последняя вещь", а не блок "try" ), но люди, угадать себя и совершать ошибки. Нет простого автоматизированного способа (кроме, например, проверки сгенерированного кода).
Конечно, вы можете просто попробовать свою функцию на большой части тестовых данных и посмотреть, если она взорвется или нет.
Компилятор F # будет генерировать команды .tail IL для всех хвостовых вызовов (если не использовать флаги компилятора для их отключения), которые используются для того, чтобы вы сохраняли фреймы стека для отладки), за исключением того, что прямые хвостовые рекурсивные функции будут оптимизированы в циклы. (EDIT: Я думаю, что в настоящее время компилятор F # также не может исправить .tail в тех случаях, когда он может доказать, что на этом сайте вызова нет рекурсивных циклов, это оптимизация, учитывая, что код операции .tail немного медленнее на многих платформах.)
'tailcall' - зарезервированное ключевое слово с идеей, что будущая версия F # может позволить вам писать, например.
tailcall func args
а затем получите предупреждение/ошибку, если это не хвостовой вызов.
Только функции, которые не являются естественно рекурсивными (и, следовательно, нуждаются в дополнительном параметре аккумулятора), будут "вынуждать" вас к идиоме "внутренней функции".
Вот пример кода, который вы задали:
let rec nTimes n f x =
if n = 0 then
x
else
nTimes (n-1) f (f x)
let r = nTimes 3 (fun s -> s ^ " is a rose") "A rose"
printfn "%s" r