Adding demos is a good idea. Notice also that we already have two substantial real usages to draw them from: the http server is implemented using suspending predicates, and the master/slave principle that launches multiple compilers during a build uses asynchronous operations and suspending predicates.
But first: suspending predicates do not tie to a thread concept. A suspending predicate is a "job" submitted to an
executionContext; the thread(s) there execute many such jobs, and a single job can jump between several threads. So terminating a thread is not the right operation at all, since it kills a part of the
executionContext rather than the job. If that context is the GUI context, you have killed the GUI thread.
Killing a dedicated thread running a "classical routine" is equally bad: it can leave critical regions locked, plus the initial-stack problem you mention. A dramatic case is killing the thread that owns the critical region used by the memory allocator/garbage collector. Within milliseconds every other thread in the program is blocked.
What you actually want is to stop the execution of a suspending predicate, and that is what cancellation does; my previous description may have been misleading here. Cancellation does not abort the job at an arbitrary instruction, but it does end the job's work: unfinished asynchronous operations are cancelled and raise exceptions in your predicate,
yield likewise raises, and these unwind the predicate through normal exception handling, so cleanup runs and the job finishes with an error. Only a long computation containing no suspension points needs
isCancelled polling, since there is nothing there to raise in.
An "initially suspended predicate" I read as a lazy one, not started until someone awaits the result. That is reasonable, and it can be built as a library facility on what we have, without compiler or runtime changes.
A clarification on modes: suspending predicates can already be
nondeterm today. What is not nondeterm is the future/promise bridge back into normal predicates. A future carries a single completion, so a call producing several solutions has no way to deliver them across that bridge.
For
determ this is easily handled: the bridge predicate is a
procedure returning an
optional{_}. So no
failed_ alternative in
result{A} is needed for that.
For several solutions, and this is also my answer to
intermediate_(A, future), the natural approach is not to make the bridge nondeterm but to let the nondeterm calls stay in the suspending world and insert the solutions into a queue that something else fetches from. What you are describing with
intermediate_ is an asynchronous stream of values, and a queue/channel gives you that today without any language change. I would not put it into
result{A} in any case: that domain is the
completion type of one future, and every existing consumer switches over its alternatives, so an alternative that can never occur for an ordinary future would force them all to handle an impossible case.
On continuation-passing style: the missing piece is not reflection but first-class continuations, i.e. capturing the continuation as a value that can be stored, passed around and resumed more than once. A
future is already a continuation, but an opaque one that is resumed once, which is also why it delivers a single result. Reflection is an unrelated thing: Scheme has full continuations and essentially no reflection, Java the reverse. Debug information would not help either, as it describes source structure rather than live runtime state.
Finally, awaiting a future from a normal thread blocks that thread until the result is ready. That may be necessary at the top level, but it is better if you can avoid waiting on the result and instead let the suspending "job" handle the result in some "asynchronous" way (putting it into a queue, posting actions to a window, or something like that).
P.S. if you find this text different (and especially longer) than what I usually write, then it is because it has been "blown-up" by an AI

.