caml-list - the Caml user's mailing list
 help / color / mirror / Atom feed
From: Neel Krishnaswami <neelk@alum.mit.edu>
To: caml-list@inria.fr
Subject: CPS folds (was Re: [Caml-list] stack overflow)
Date: Wed, 9 Apr 2003 10:14:53 -0400	[thread overview]
Message-ID: <16020.10973.162317.709095@h00045a4799d6.ne.client2.attbi.com> (raw)
In-Reply-To: <200304091723.30890.yangsx@fltrp.com>

Yang Shouxun writes:
> On Wednesday 09 April 2003 16:14, Markus Mottl wrote:
> >
> > The trick is to use continuation passing style (CPS): you pass a
> > function closure (continuation) containing everything that's
> > needed in subsequent computations.  Instead of returning a result,
> > the sub-function calls the continuation with the result, which
> > makes the functions tail-recursive.
> 
> I've learned this style in Scheme. Yet I feel paralyzed when trying
> to write in it to build trees. The problems are that unless the next
> call returns, the tree is not complete yet and it may have several
> calls on itself.

You are 90% of the way to figuring out how to use CPS in this
situation, actually! The trick is to use continuation functions to
represent the computation yet to be done. I'll illustrate with
an example -- let's take a tree type:

type 'a tree =
  | Leaf
  | Node of 'a tree * 'a * 'a tree

Now, let's write a regular, recursive fold for this datatype:

let rec fold ~leaf ~node tree =
  match tree with
  | Leaf -> leaf
  | Node(left, x, right) ->
      let v1 = fold ~leaf ~node left in
      let v2 = fold ~leaf ~node right in
      node v1 x v2

val fold : leaf:'a -> node:('a -> 'b -> 'a -> 'a) -> 'b tree -> 'a

As you expect, this particular fold exhibits non-constant
stack growth, because there are function calls that aren't in
tail position:

  | Node(left, x, right) ->
      let v1 = fold ~leaf ~node left in   (* Not a tail-call *)
      let v2 = fold ~leaf ~node right in  (* Not a tail-call *)
      node v1 x v2

What we want to do is to make all of the recursive calls to fold
tail-recursive, so that they don't grow the stack. Let's be
stupid simple for a second, and throw away all of things keeping that
from happening:

let rec tr_fold ~leaf ~node tree =
  match tree with
  | Leaf -> leaf
  | Node(left, x, right) ->
      tr_fold ~leaf ~node left 

Now tr_fold is tail-recursive, at the cost of forgetting how to
call itself on the right subtree and applying the node function.
Let's fix that by adding a new parameter to tr_fold that will
keep track of that -- and the value that tracks "stuff to execute"
is a function:

let rec tr_fold' ~leaf ~node tree k =
  match tree with
  | Leaf -> k leaf  (* Call leaf to "stuff to execute" *)
  | Node(left, x, right) ->
      tr_fold' ~leaf ~node left  (* Now a tail-call! *)
	(fun v1 ->
	  let v2 = tr_fold' ~leaf ~node right k in (* Not a tail-call *)
	  node v1 x v2)

tr_fold' :
  leaf:'a -> node:('a -> 'b -> 'c -> 'c) -> 'b tree -> ('a -> 'c) -> 'c

Now we just need to repeat the exercise on the second let body,
and we get:

let rec kfold ~leaf ~node tree k =
  match tree with
  | Leaf -> k leaf
  | Node(left, x, right) ->
      kfold ~leaf ~node left 
	(fun v1 ->
	  kfold ~leaf ~node right
	    (fun v2 -> k (node v1 x v2)))

val kfold :
  leaf:'a -> node:('a -> 'b -> 'a -> 'a) -> 'b tree -> ('a -> 'c) -> 'c

At this point, kfold has no stack growth in it, because every
self-call it makes is a tail call. All of the control context is
stored in the closures -- the continuations -- we have build during
its execution. You can see the close correspondence between the
original and the CPS version by playing a few games with formatting:

  | Node(left, x, right) ->
      let v1 = fold ~leaf ~node left in   
      let v2 = fold ~leaf ~node right in  
      node v1 x v2

vs:

  | Node(left, x, right) ->
      kfold ~leaf ~node left  (fun v1 ->
      kfold ~leaf ~node right (fun v2 -> 
        k (node v1 x v2)))

You can see that what we do in each case is "compute the left/right
subtree and then bind the result to v1/v2". Finally, you can get back
the original fold by passing in the identity function as the "starter"
continuation:

let fold ~leaf ~node tree = kfold ~leaf ~node tree (fun x -> x)

val fold : leaf:'a -> node:('a -> 'b -> 'a -> 'a) -> 'b tree -> 'a


(As an aside: Yes, I didn't completely CPS-convert the program -- the
node function is not in CPS-form in kfold.)

-- 
Neel Krishnaswami
neelk@alum.mit.edu

-------------------
To unsubscribe, mail caml-list-request@inria.fr Archives: http://caml.inria.fr
Bug reports: http://caml.inria.fr/bin/caml-bugs FAQ: http://caml.inria.fr/FAQ/
Beginner's list: http://groups.yahoo.com/group/ocaml_beginners


  parent reply	other threads:[~2003-04-09 14:12 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2003-04-09  2:10 [Caml-list] stack overflow Yang Shouxun
2003-04-09  2:19 ` brogoff
2003-04-09  2:45   ` Yang Shouxun
2003-04-09  8:14     ` Markus Mottl
2003-04-09  9:23       ` Yang Shouxun
2003-04-09 11:34         ` Markus Mottl
2003-04-10  4:12           ` Parallel CPS? (was Re: [Caml-list] stack overflow) Yang Shouxun
2003-04-10  4:58             ` Mike Lin
2003-04-09 14:14         ` Neel Krishnaswami [this message]
2003-04-09 16:54           ` CPS folds " brogoff
2003-04-09 17:23             ` Mike Lin
2003-04-09  2:43 ` [Caml-list] stack overflow David Brown
     [not found] ` <200304091034.45256.yangsx@fltrp.com>
     [not found]   ` <16019.34434.468479.586884@barrow.artisan.com>
2003-04-09  2:53     ` Yang Shouxun
2003-04-09  6:45 ` David Monniaux
2003-04-13 15:42 ` John Max Skaller

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=16020.10973.162317.709095@h00045a4799d6.ne.client2.attbi.com \
    --to=neelk@alum.mit.edu \
    --cc=caml-list@inria.fr \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).