[MLton-user] simple, portable, asynchronous programming in SML

Stephen Weeks sweeks@sweeks.com
Mon, 31 Jul 2006 17:51:32 -0700


> Do you have an idea of what this low-level asynchronous system
> "interface" should look like?  This seems to me to be where a lot
> off issues are likely to hide.

There's nothing to add to the EVENT signature; it's simply a question
of arranging for runHandlers to be called.  I have one example where
I've used this library for a GUI, and the low-level stuff was really
quite easy.  In that case, the GUI had a way to attach callbacks to
receive notifications of UI events.  I simply had each callback enable
the appropriate SML event and call runHandlers.  All in all, less than
fifty lines of code.  I don't see why it should be any more difficult
in other cases.

> Off the top of my head, it seems that you need some sort of event
> loop that will continually poll all of the I/O related events,
> enabling them when appropriate.

My perhaps naive thinking is that in any such system, there is a way
to have the system run code when its events become enabled and that
one can arrange for that code to enable the SML events and call
runHandlers, just as I did in my one GUI example.

> It isn't clear to me that this sort of event loop won't require the
> main program to be written in the "inverted program structure"
> criticized by Reppy [Concurrent Programming in ML, p. 145].

I don't see this at all.  There may be an event loop driving
runHandlers, but that happens at some very low level and is never seen
by the bulk of SML code.

Inverted program structure is more connected with whether one has
"sync" or not.  Without sync, one has no way to perform an
asynchronous computation deep in the middle of some call stack, and so
must manually express everything that might need to block in terms of
events (somewhat akin to manual CPS conversion).

> It is probably the case that higher-order functions in ML make this
> a little easier than it might be in a language like C, but without
> preemption, the programmer must be careful about long running code
> executing in handlers.

I agree, but want to point out that preemption is a separate issue and
there are situations where it is not necessary (and hence the
complexity of introducing it is a mistake).  First, there are some
situations where one doesn't care if code runs for a while and
prevents other code from running -- one simply wants all the code to
run.  Second, there are some situations where one knows that code
isn't long running.  And third, even if one does need a long-running
background computation while still handling events, one can
judiciously use an operation like Thread.pause in the background
computation to allow events to be handled.  Then one still has a clear
idea of when interleaving can happen.

What really causes inverted program structure is the absence of
Thread.pause, not the absence of preemption.

I'll also point out that preemption can be separated into two pieces
semantics and implementation.  Normally, one has non-preemptive or
preemptive for both semantics and implementation.  However, I can
imagine a system with non-preemptive semantics but a preemptive
implementation (e.g. using STM tricks).  That could help in situations
with a long-running background process or where one needs good
responsiveness, while still giving a nicer programming model.


Since I'm back on this thread, I'll respond to Ville's earlier mail as
well.  I largely agreed with everything he said.  I would like to
point out that the benefit of expressing asynchronicity in the types
tilts the scales somewhat back toward event-based programming instead
of non-preemptive threading, since it's always better to have more in
the types.  In my limited experience (i.e. with the GUI system I
mentioned) I found that I was able to do everything easily using the
Event module above even in situations that I thought initially
required threads.  And, I found the help from the type system quite
useful.  Event.wrap really made the chopping up of code into chunks
quite palatable.

As another example, which one can already see in the code I sent,
consider my implementation of multicast and compare it with the
implementation that Reppy gives in section 5.5 of the CML book.  My
implementation uses events, while Reppy's uses threads.  In the end,
the implementations do very similar things, but I think it is arguable
that my implementation is simpler, and shows that an appropriate
combination of "when" and recursion nicely achieve the effect of a
separate thread.  I'm not claiming any general result here -- simply
that in more situations than I would have thought, non-preemptive
threads aren't necessary and don't make things easier.