The draft quite sanely protects C code from always having to cope with
multiple returns (not to mention state-saving) as via upward
continuations. C code should not, however, be _prevented_ from
constructing an upwards continuation.
The draft is silent on tail call optimization which is an unfortuante
omission that limits, for example, the kinds of higher-order
procedures that can be implemented using the FFI.
It's silent about asynchronous interrupts which, while not standard
Scheme, are likely to be an important feature of most
implementations. In an interactive application especially, absense of
support for asynchronous interruption in "built-in" procedures can
spoil an application's usability.
It uses non-local exits for errors which raises the unwind-protection
issues I mentioned earlier.
I would therefore like to suggest the draft be revised on four
points:
1) Don't use non-local exits for errors. Instead, provide a
mechanism for returning error codes. If Scheme return values are
to be passed via output parameters, then there is a convenient
interface for this:
error_code = SCHEME_CAR (&return_value, instance, &pair);
if (error_code)
{
/* clean up and return an error to our caller */
return error_code;
}
/* otherwise, continue normally. The call to CAR
* succeeded.
*/
In addition to avoiding non-local exits in the FFI, this style of
error handling is closer to what is customary in C.
2) Do provide polling for asynchronous interruption.
As in:
while (very_long_loop)
{
...
if (SCHEME_INTERRUPT_POLL (instance))
{
/* cleanup and return early */
return SCHEME_ERR_INTERRUPT;
}
...
}
3) Do provide a "trampoline-on-the-ceiling" style of tail calls for C.
As in:
scheme_error_code
fn ( [...] )
{
[...];
/* instead of returning a value, something vaguley like:
*/
SCHEME_CONSTRUCT_APPLICATION (&result, instance, &proc,
&arg1, &arg2, ...);
return SCHEME_ERR_MAKE_TAILCALL;
}
in which case my caller is responsible for performing the procedure
application I constructed.
While certainly not as fast as what an implementation can do
internally, such tail calls are at least safe-for-space and
portable.
The converse of that is that the FFI needs to specify how to
call a function which might request the caller to complete a
tail call.
4) Do provide upward continuations from C.
I don't have an interface sketch but, briefly, while the
current interfaces for call-outs are fine, it should also
be possible to write a C primitive that accepts as input a
first-class representation of its continuation and returns
the same.
Used in a manner similar to the mechanism shown above for
tail calls, this can permit FFI-using primitives whose
state can be captured in an upward continuation.
-t