caml-list - the Caml user's mailing list
 help / color / mirror / Atom feed
* [Caml-list] HOFs, recursion, and being tail-rec...
@ 2006-02-12 22:08 Jonathan Roewen
  2006-02-12 23:53 ` Jacques Garrigue
  0 siblings, 1 reply; 5+ messages in thread
From: Jonathan Roewen @ 2006-02-12 22:08 UTC (permalink / raw)
  To: OCaml

Hi,

I have a simple implementation of depth-first-search, and was
wondering if my approach would qualify as tail-rec (whether from the
code it is/isn't, and whether ocaml can optimise it so it is).

val positions : 'a -> ('a * 'a) list -> 'a list -> 'a list
(* I think that's right type: returns positions we can traverse to,
omitting nodes we've previously visited *)

(* val dfs: 'a -> 'a -> ('a * 'a) list -> bool *)
let dfs start goal edges =
    let rec search visited position =
      if position = goal then true
      else List.exists (search (position::visited)) (positions
position edges (position::visited))
    in search [] start;;

Jonathan


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [Caml-list] HOFs, recursion, and being tail-rec...
  2006-02-12 22:08 [Caml-list] HOFs, recursion, and being tail-rec Jonathan Roewen
@ 2006-02-12 23:53 ` Jacques Garrigue
  2006-02-13  2:05   ` skaller
  0 siblings, 1 reply; 5+ messages in thread
From: Jacques Garrigue @ 2006-02-12 23:53 UTC (permalink / raw)
  To: jonathan.roewen; +Cc: caml-list

From: Jonathan Roewen <jonathan.roewen@gmail.com>
> 
> I have a simple implementation of depth-first-search, and was
> wondering if my approach would qualify as tail-rec (whether from the
> code it is/isn't, and whether ocaml can optimise it so it is).

By definition a depth-first-search cannot be tail-recursive: you need
a stack to implement the backtracking.
There is a degenerate case where all nodes are non-branching
(i.e. there is only one path), which in theory could be made
tail-recursive. But it would not be the case with your code, as
List.exists has no special case for the last element of the list (not
that it would make a lot of sense in general.)

> val positions : 'a -> ('a * 'a) list -> 'a list -> 'a list
> (* I think that's right type: returns positions we can traverse to,
> omitting nodes we've previously visited *)
> 
> (* val dfs: 'a -> 'a -> ('a * 'a) list -> bool *)
> let dfs start goal edges =
>     let rec search visited position =
>       if position = goal then true
>       else List.exists (search (position::visited)) (positions
> position edges (position::visited))
>     in search [] start;;


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [Caml-list] HOFs, recursion, and being tail-rec...
  2006-02-12 23:53 ` Jacques Garrigue
@ 2006-02-13  2:05   ` skaller
  2006-02-13  2:47     ` Jonathan Roewen
  0 siblings, 1 reply; 5+ messages in thread
From: skaller @ 2006-02-13  2:05 UTC (permalink / raw)
  To: Jacques Garrigue; +Cc: jonathan.roewen, caml-list

On Mon, 2006-02-13 at 08:53 +0900, Jacques Garrigue wrote:
> From: Jonathan Roewen <jonathan.roewen@gmail.com>
> > 
> > I have a simple implementation of depth-first-search, and was
> > wondering if my approach would qualify as tail-rec (whether from the
> > code it is/isn't, and whether ocaml can optimise it so it is).
> 
> By definition a depth-first-search cannot be tail-recursive: you need
> a stack to implement the backtracking.

There is a need for a stack, but it doesn't have to be
a machine (control) stack. A basic principle of duality
seems to be that control and data can always be transformed
into each other (proof: Turing only has conditional goto). 
In this case CPS provides the transform.

There is category error in Jacques claim: depth-first search
is an algorithm, it is a matter of *semantics*.

Tail-rec is merely a *syntactic* property, which has
semantic implications only for a particular implementation.

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).

BTW: considering control/data duality you will find that most compilers
miss very interesting optimisations. Ackermann's function
can be implemented using only 2 words (for the arguments)
per stack frame. No compilers I know of do this optimisation --
and I have not seen any reference to it in the literature.


-- 
John Skaller <skaller at users dot sf dot net>
Felix, successor to C++: http://felix.sf.net


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [Caml-list] HOFs, recursion, and being tail-rec...
  2006-02-13  2:05   ` skaller
@ 2006-02-13  2:47     ` Jonathan Roewen
  2006-02-13  3:23       ` skaller
  0 siblings, 1 reply; 5+ messages in thread
From: Jonathan Roewen @ 2006-02-13  2:47 UTC (permalink / raw)
  To: skaller; +Cc: caml-list

> 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. BTW: the memory linear to the tree depth is used in the
list passed to search, 'visited'.

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). As it wouldn't be hard to make it tail-rec I'd imagine..

Jonathan


^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [Caml-list] HOFs, recursion, and being tail-rec...
  2006-02-13  2:47     ` Jonathan Roewen
@ 2006-02-13  3:23       ` skaller
  0 siblings, 0 replies; 5+ messages in thread
From: skaller @ 2006-02-13  3:23 UTC (permalink / raw)
  To: Jonathan Roewen; +Cc: caml-list

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


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2006-02-13  3:23 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2006-02-12 22:08 [Caml-list] HOFs, recursion, and being tail-rec Jonathan Roewen
2006-02-12 23:53 ` Jacques Garrigue
2006-02-13  2:05   ` skaller
2006-02-13  2:47     ` Jonathan Roewen
2006-02-13  3:23       ` skaller

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).