[Prev][Next][Index][Thread]
Re: Dylan Features
In article <c1Wi5.106299$1h3.1647179@news20.bellglobal.com>, "Maury
Markowitz" <maury_markowitz@hotmail.com> wrote:
> > something like Scheme's call-with-current-continuation? Thanks!
>
> Newbie Q, what is this?
Do you know "continuation-passing style"? It's a way of thinking about
programs (and implementing them) that does not use the normal CPU return
address stack to implement function return -- or put alternatiely, that
makes function return explicit instead of implicit.
Take the standard factorial function:
define method fact(n)
if (n = 0)
1
else
n * fact(n - 1)
end
end;
You can convert this (manally or automatically) into
"continuation-passing style":
define method fact(n, return)
if (n = 0)
return(1)
else
fact(n - 1, method (val) return(n * val) end)
end
end;
Now, instead of calling this as...
format-out("factorial 5 is %=\n", fact(5));
... you'd call it as ...
fact(5, method (val) format-out("factorial 5 is %=\n", val) end);
In fact, in true CPS, all the built-ins such as =, *, -, and format-out
will expect to be used in CPS as well:
define method fact(n, return)
\=(n, 0, method (bool)
if (bool)
return(1)
else
\-(n, 1, method (subresult)
fact(subresult, method (factresult)
\*(n, factresult, method (timesresult)
return (timesresult)
end)
end)
end)
end
end)
end;
method testfact(donext)
fact(5, method (factresult)
format-out("factorial 5 is %=\n", factresult, donext)
end)
end;
There are all sorts of interesting properties of CPS. It makes certain
sorts of compiler optomisations easier (and others harder).
The most striking property is that no function *EVER* falls off the end
and returns. You *always* end up with a tail-call to a function that
itself never returns. This in effect turns function call into a simple
"goto". And of course makes tail-call optomization mandatory if you
want to actually program this way on real hardware.
Another interesting property is that it makes passing arguments and
passing back function results symmetrical. Why can you pass multiple
arguments but not multiple results in most languages? Because that's
the way the historical function call ABI is set up. Well, with CPS you
never actually return, you just call the guy who is going to use your
results -- and if there is more than one of them then it's just a normal
function call with more than one argument. No problem.
When you look at how CPS programs end up in machine code it's very
interesting. What you find is that firstly a whole bunch of different
functions end up in memory next to each other with good old
spaghetti-code goto's between them and no use of the hardware function
call/return instructions.
The second thing you find is that the "branch and link" instructions
found in such CPUs as the PowerPC (and several other RISCs) and even in
older designs such as the PDP-11 are actually *ideal* for implementing
CPS.
To really understand this stuff I suggest that you find the 1977 paper
by Steele et al "Lambda: The Ultimate Declarative". It's extremely
interesting and well-written and is relevant to Dylan in that it
describes a lot of the thinking that lead to Scheme, and on which Dylan
in based.
Back to your question: what is call-with-current-continuation?
call-with-current-continuation is something that lets you mix CPS and
ordinary style in the same program. It's basically the high-level
equivalent to "JSR" or "branch-and-link" at the machine level, in that
those low-level instructions are themethod by which a program can gain
access to the address o the next instruction to be executed and save it.
If Dylan had call-with-current-continuation then you could write this:
define method fact(n, return)
if (n = 0)
return(1)
else
fact(n - 1, method (val) return(n * val) end)
end
end;
format-out(
"factorial 5 is %=\n",
call-with-current-continuation( method(continuation)
fact(n, continuation)
end)
)
In fact, you *can* write this in Dylan, and it looks like this:
format-out(
"factorial 5 is %=\n",
block (continuation)
fact(n, continuation)
end
)
Everything I showed above and -- as far as I recall -- nearly everything
in the Steele paper can actually be done in Dylan using "block".
The diffeence between Scheme and Dylan is that in Dylan the function
bound to the name "continuation" is valid only until you exit from the
block() ... end construct. In Scheme you can take this value and store
it somewhere and use it later -- maybe even several times. it's very
much like the idea of a "closure", but applied to flow-of-control
instead of to data.
C doesn't have closures. The fact that Dylan has closures implies that
some data that looks as if it could be stored on the stack (and could be
in C) in fact has to be stored on the heap. Closures in effect create
"objects".
Continuations do the same thing for flow-of-control. The fact that
Dylan doesn't have full call-with-current-continuation means that
function call/return can happen on a stack. In Scheme there are some
function invocations that actually need to be stored on the heap.
call-with-current-continuation with unlimited extent in effect creates
"threads".
A good example of a program written in CPS is the old "Thomas" compiler,
which implements prefix-syntax Dylan on top of Scheme. You can still
find it on the net and reading the code is very interesting.
-- Bruce
Follow-Ups:
References: