[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: continuations and linear types
On Wednesday, April 17, 2002, at 02:58 PM, Allen Short wrote:
> i'm very excited about the possibilities goo presents; it's almost
> everything i've wanted from a language.
>
> my primary interest is in distributed and concurrent apps; hence, i've
> been quite interested in the disucssions of continuations and threads so
> far. I propose adding linear types to goo;
> [.....]
After an initial reading of that paper, I'm quite unconvinced that
linear types as described there are a useful concept. As far as I can
tell, the main argument for them is to make it impossible for one to be
accessed by multiple threads. However, this is a _*great*_ cost in
complexity for single-threaded programs. I can tell you right now I'd
hate to use a language that makes me go through the hoops described in
that paper. Even they admit its basically impossible to use without a
GUI programming UI. What are the chances of that ever getting done in
the near future? They poo-poo text-programming, but I haven't seen any
indication that its going to go away any time soon. The rest of the
arguments seem to me like they're basically premature optimizations
exposed to the programmer.
However, I do believe that a similar concept, not applied to number of
references, but rather, to number of *different threads that have
references* is a great idea. In fact, I'm currently a proponent of the
concept that objects should *always* be linear-with-respect-to-threads.
That is, two threads cannot ever hold a reference to the same object
[[at least, as far as they can tell]]. Make RPC the only method of
communication, but make it very optimized. In my experience, when
writing a threaded program, you pretty much need to work that way
anyways if you're going to stay sane, but its just a lot easier to screw
up and allow a reference to escape to another thread when you didn't
intend to.
> Additionally, linear continuations have unique advantages; they allow
> continuations to be stack allocated (since they cannot be captured), and
> avoid the bad interactions shareable continuations have with
> unwind-protect and tail recursion (for a detailed discussion of this
> issue, see http://lists.tunes.org/archives/tunes/1999-August/002235.html
> ) while providing most of the control advantages of shareable
> continuations such as coroutines and microthreading.
>
> Of course, some applications require non-linear continuations;
> Queinnec-style use of continuations for web apps most notably, as well
> as prolog-style backtracking. My own feelings are that only allowing
> linear continuations is a sufficient tradeoff between expressitivity and
> clean semantics/efficient implementation.
Single-shot continuations have a lot going for them, but unless I'm
missing something, being able to be stack-allocated is not one of them.
However, every time I think about multi-callable continuations they seem
like a worse idea. That message reminds me of my biggest objection: that
functions can get put in normally-impossible states of execution. Even
for web-applications it seems like a poor idea, as the state of the data
is not copied, only the state of execution. The That's all well-and-good
as long as you make sure not to ever modify program state or ever use
non-functional operations in your app. If anything does, it could end up
in serious trouble. That seems to me to be quite poor, and it makes me
think that what you really want to have for web-applications is fork().
Then there's the issues with callbacks from a C library. With one-shot
continuations, it seems at least *possible* to make the C
interoperability issues work out, but with multi-shot continuations,
that gets more complicated. Its not really possible to return to a C
function more than once.
Banging out a microthread implementation in psudeo-GOO to demonstrate to
myself impossibility of stack-allocating frames [gonna need it sometime,
why not write it in an email program with no indenting or parenmatching?
;P]:
(dv *threads* (tup))
(df yield ()
(call-cc
(fun (cont)
(if (> 0 (len *threads*))
(seq
(def next (1st *threads*)
(set *threads* (add (tail *threads*) cont) ; substitute the
appropriate generic equivalent of "tail" in GOO
(next))
(cont)
))))
(df die ()
(msg out "Thread died")
(def next (1st *threads*)
(set *threads* (tail *threads*))
(next))
(df add-thread (func)
(msg out "Thread started")
(set *threads*
(add *threads*
(fun ()
(func)
(die))))
(df foo ()
(msg out "Foo frame allocated")
(add-thread bar)
(yield)
(msg out "Foo frame deallocated")
)
(df bar ()
(msg out "Bar frame allocated")
(yield)
(msg out "Bar frame deallocated")
)
=> (foo)
Foo frame allocated
Thread started
Bar frame allocated
Foo frame deallocated
now foo has returned to top-level, but bar's activation frame must still
exist, so if yield is called again,
=> (yield)
Bar frame deallocated
Thread died