Browse thread
[Caml-list] HOFs, recursion, and being tail-rec...
[
Home
]
[ Index:
by date
|
by threads
]
[ Message by date: previous | next ] [ Message in thread: previous | next ] [ Thread: previous | next ]
[ Message by date: previous | next ] [ Message in thread: previous | next ] [ Thread: previous | next ]
Date: | 2006-02-13 (03:23) |
From: | skaller <skaller@u...> |
Subject: | Re: [Caml-list] HOFs, recursion, and being tail-rec... |
On Mon, 2006-02-13 at 15:47 +1300, Jonathan Roewen wrote: > > So it isn't a well formed sentence to say depth first > > search cannot be tail-rec, it is easy to construct a > > depth first search in any FPL that is. > > > > However, no matter what you do, you will indeed need > > memory linear in the tree depth (unless it is 1-ary tree as > > pointed out). > > Yes, but tail-rec would mean the function call trace does not use > memory linear in the tree depth, which would be a positive > optimisation. I do not think you can assume that without measuring it, either by an actual speed test, or examining the generated machine code. > BTW: the memory linear to the tree depth is used in the > list passed to search, 'visited'. Yes. > I'm just wondering if using List.exists and HOFs prevents the compiler > from generating tail-rec code (I presume that List.exists is already > tail-rec). I am not an expert on Ocaml optimisation -- but I would guess your code does indeed cause the machine stack to be pushed with the return address of 'search': let rec search visited position = if position = goal then true else List.exists (search (position::visited)) (positions position edges (position::visited)) Here 'search' is called to calculate an argument to List.exists, which is the last call, and the one in tail position. Well, actually this is not correct! The function in tail position *in theory* is actually (List.exists (search (position::visited))) since application is left associative. However Ocaml is smart and I believe it knows how to make curried calls of explicitly named functions without closure construction. In other words, if possible, it calls let f x y = .. in f a b (* f is NOT in tail position *) as if you had written let f (x,y) = .. in f (x,y) (* f IS in tail position *) This just shows that 'tail-rec' is a muddled idea. It is a purely syntactic property, when what you're interested in is performance. The correlation is implementation dependent. In any case, 'search' call in your code is NOT in tail position within the function search any way you look at it. I'd be surprised if Ocaml could optimise the code above to eliminate recursive calls. There IS a way to do this in some cases that I know about, which I hope to implement in Felix one day: a significant class of non-tail recursive functions can in fact be implemented without subroutine calling. I think your code is an example of this. > As it wouldn't be hard to make it tail-rec I'd imagine.. Generally KISS is a good idea for two reasons which are the same reason: (a) you can reason about your code more easily (b) the compiler optimiser can reason about your code more easily In the case of a balanced tree there is no reason to worry about recursion. The formula for the size of the tree is exponential in the tree depth, so a couple of recursions on a linear memory stack is irrelevent compared to the disk paging you'd get thrashing about trying to search a tree of any significant depth. As a counter example, I think it was Jacques who actually found Felix lexer was scanning a list of tokens non-tail recursively, which had a significant impact on Felix performance -- causing stack overflow lexing larger files. That's the degenerate case of an 1-ary tree :) -- John Skaller <skaller at users dot sf dot net> Felix, successor to C++: http://felix.sf.net