Reputation: 1651
I wrote the follwing function:
let str2lst str =
let rec f s acc =
match s with
| "" -> acc
| _ -> f (s.Substring 1) (s.[0]::acc)
f str []
How can I know if the F# compiler turned it into a loop? Is there a way to find out without using Reflector (I have no experience with Reflector and I Don't know C#)?
Edit: Also, is it possible to write a tail recursive function without using an inner function, or is it necessary for the loop to reside in?
Also, Is there a function in F# std lib to run a given function a number of times, each time giving it the last output as input? Lets say I have a string, I want to run a function over the string then run it again over the resultant string and so on...
Upvotes: 17
Views: 3737
Reputation: 118925
Edit: Since F# 8 there is a way with the [<TailCall>]
attribute, see this answer: https://stackoverflow.com/a/77532717/969070
Original answer:
Unfortunately there is no trivial way.
It is not too hard to read the source code and use the types and determine whether something is a tail call by inspection (is it 'the last thing', and not in a 'try' block), but people second-guess themselves and make mistakes. There's no simple automated way (other than e.g. inspecting the generated code).
Of course, you can just try your function on a large piece of test data and see if it blows up or not.
The F# compiler will generate .tail IL instructions for all tail calls (unless the compiler flags to turn them off is used - used for when you want to keep stack frames for debugging), with the exception that directly tail-recursive functions will be optimized into loops. (EDIT: I think nowadays the F# compiler also fails to emit .tail in cases where it can prove there are no recursive loops through this call site; this is an optimization given that the .tail opcode is a little slower on many platforms.)
'tailcall' is a reserved keyword, with the idea that a future version of F# may allow you to write e.g.
tailcall func args
and then get a warning/error if it's not a tail call.
Only functions that are not naturally tail-recursive (and thus need an extra accumulator parameter) will 'force' you into the 'inner function' idiom.
Here's a code sample of what you asked:
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
Upvotes: 24
Reputation: 3644
The compiler can check this for you, as of F# 8! Mark the function with TailCallAttribute
, and you'll get a warning if you provide an implementation that isn't tail recursive.
For example, this code:
[<TailCall>]
let rec fibNaive n =
if n <= 1 then n
else fibNaive (n - 1) + fibNaive (n - 2)
emits two instances of Warning FS3569 : The member or function 'fibNaive' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way.
(one for each recursive call)
While this code emits no warnings:
[<TailCall>]
let fibFast n =
let rec fibFast x y n =
match n with
| _ when n < 1 -> x
| 1 -> y
| _ -> fibFast y (x + y) (n - 1)
fibFast 0 1 n
For bonus points, it can be a good idea to turn this into an error, by adding <WarningsAsErrors>FS3569</WarningsAsErrors>
to your fsproj.
Upvotes: 4
Reputation: 5252
I like the rule of thumb Paul Graham formulates in On Lisp: if there is work left to do, e.g. manipulating the recursive call output, then the call is not tail recursive.
Upvotes: 3