#---
The PingPong program simulates client-server communication.
Here the client sends a request to the server that says "ping",
and the client answers "pong".
+++
use console #--- First let us see a functional version of ping and pong to get
used to the concept. The client simply calls ping n times, and
puts "pong" when it returns.
+++
__DOC "functping: functional version of ping "Prints 'ping' and returns to pong "" def functping(*Stdout con) -> return()
{
console!con.Puts("ping")
}

__DOC "functpong: functional version of pong "Uses for to call ping n times. As for evaluates as needed, "all is used to evaluate all the terms. The recursive version "(without for) is left as an exercise to the reader. "" def functpong(int n, *Stdout con) -> return()
{
[for i in 1...n
functping(con) ->
console!con.Puts("pong")
].list
}


#--- Now to the real thing. This time it is ping that stops after
it has produced n "ping"s, the client just keeps requesting
the server until the server says done.

This example illustrates first-class continuations. However I
am not sure that I want first-class continuations in dodo, as
the code can get quite confusing. This is why I did not define
a type for them.
+++
__DOC "ping with first-class continuation "Note: this is not valid dodo syntax. Comment this part to run. "Because the parameter of yield1 is a continuation, we use ? as "type since dodo has no type defined for continuations. Here "callback() is the equivalent of call/cc in other languages. "As before, we use for to put 'ping' n times. The callback "call sends the control back to the caller while saving the "current stack, so that the computation can continue when "ping regains control. "" def invalidping(int n, *Stdout con) -> yield1(?) | done()
{
fun => c(): yield1(c). -> callback
[for i in 1...n
console!con.Puts("ping") ->
callback()
].list ->
done()
} __DOC "pong with first-class continuation "Send a request to ping and receive a handle. Then put 'pong' "and give the control back to ping using the handle. The value "of the handle is updated for next iteration (Note: an empty "continuation ->| defaults to returning its arguments). When "ping is done, return. "" def invalidpong(int n, *Stdout con) -> return() { invalidping(n, con) -> pingcallback # for comprehension syntax: # [for index = initialvalue; where condition; then nextvalue # ...] -- we skip where and use continue() instead of then. [for next = pingcallback console!con.Puts("pong") -> (next() -> c continue(c) # update next ping callback or... | return() # done ) ].list | return() # done if n=0 } #--- The final version uses the yield and resume constructs to replace first-class continuations. It is identical to the version with first-class continuations except that resume is the only allowed operation on the handle. +++ __DOC "ping with yield "Use for to put n 'ping's, allowing pong to run after each. "The yield call sends the control back to the caller while "saving the current stack, so that the computation can "continue when ping regains control. "" def ping(int n, *Stdout con) -> yield(@) | done() { [for i in 1...n console!con.Puts("ping") -> yield() ].list -> done() } __DOC "pong with resume "Send a request to ping and get the hand. Then put 'pong' "and give the control back to ping using resume. The handle "is updated at every yield. When ping is done, return. "" def pong(int n, *Stdout con) -> return() { ping(n, con) -> next [for; console!con.Puts("pong") -> (resume next -> continue() # use next ping callback or... | return() # done ) ].list | return() # done if n=0 } # Call pong with 15 bounces def Main()
{
pong(15, Stdout())
}