> > a. Ease of implementation should never be the first argument;-)
> No.  Speed should be.  I do use MVars a lot via the UniForM workbench,
> and the most important thing is to have something simple and lightweight
> that can be built upon.  If I were offered a 100% increase in
functionality
> in exchange for a 10% slowdown, I would refuse it.

Even if speed really is your first argument, it is not your only
one - otherwise you wouldn't be using Haskell. You don't want
your applications to crash unpredictably just because the most
efficient (partial) implementation of your programming language
has to leave out some safety checks. There is a language that
assumes that programmers better know exactly what they are doing
if they want to get fast code. It is called "C".

> The fact is that MVars are rightly incredibly primitive.  If you are doing
> anything advanced with concurrency you need a better way of communicating,
> such as channels, or the first-class events that Einar Karlsen put into
the
> UniForM workbench.  But both these can be built easily from MVars.

Right. I am relieved to read that your application source is not
full of calls to putMVar, but rather uses safe abstractions built
on top of MVars (it might be interesting to isolate them out of
your current application and migrate them to a CH library?).

----------------------------------------------------

Now, I was talking about some modifications that would not easily
be built from MVars (as they are) and about an operational
semantics that introduces exceptions where they are not needed.
This indicates that the current MVars may not be as primitive and
complete as we think.

But let me rephrase the situation so that you can see my problem
more easily (if you don't want to follow the details, skip to
the last section).

Currently, the basic interface to CH is something like

I
1. forkIO       :: IO a -> IO a
2. sleep        :: Int -> IO a
3. newEmptyMVar :: IO (MVar a)
4. takeMVar     :: MVar a -> IO a       -- blocking
5. putMVar      :: MVar a -> a -> IO () -- non-blocking,
                                        -- may throw exception

Note the asymmety and the side condition.  And here is a list of
more primitive operations that should allow to implement this
interface:

II
1-4 as above
5'. putMVar'          :: MVar a -> a -> IO () -- blocking
6. tryButDoNotSuspend :: IO a -> IO a -> IO a

where (6) tries to execute its first parameter, and if this
would lead to an immediate suspension of the current thread, it
executes its second parameter instead. Then

5. putMVar mv v = tryButDoNotSuspend (putMVar' mv v)
                                     (throw PutFullMVar)

as well as

4'. takeMVar' mv = tryButDoNotSuspend (takeMVar mv >>= return.Just)
                                      (return Nothing)

as an implementation of tryTakeMVar.

IMHO, if you want to try a take or put operation, you might as
well make a honest attempt. Otherwise, you don't give other
threads the chance to fill or empty the MVar you are looking at.
So I would want to change 6 to

6'. tryForaWhileButDoNotBlockForever :: IO a -> Int -> IO a -> IO a

where (6') tries to execute its first parameter but if that would
block, sets a timeout (second parameter) for the operation.  If
the operation is still blocked after timeout, it is deleted and
replaced by executing the third parameter.

----------------------------------------------------

Let's sum things up: Using II as a basis, the current CH
interface could be implemented as

III
1. CH.forkIO       = II.forkIO
2. CH.sleep t      = II.tryForaWhileButDoNotBlockForever
                          (II.newEmptyMVar >>= II.takeMVar)
                          t
                          (return ())
3. CH.newEmptyMVar = II.newEmptyMVar
4. CH.takeMVar     = II.takeMVar
5. CH.putMVar mv v = II.tryForaWhileButDoNotBlockForever
                          (II.putMVar' mv v)
                          0
                          (throw PutFullMVar)

----------------------------------------------------

This pseudo-implementation allows me to point out the problems
I have with current CH. I would like to have

  - both take and put blocking by default
  - non-blocking variants of both take and put available
  - more sensible timeouts (>0) for the non-blocking variants
  - less disruptive default actions for the non-blocking variants
    (probably MayBe instead of exceptions)

I have no objections if others are happy with the current
interface, with the definition suggested in III, or even want to
use the operations in II directly. I am just saying that II looks
more natural and slightly more primitive to me (II can support
CH, but not vice-versa?).

Implementation-wise, both tryButDoNotSuspend and
tryForaWhileButDoNotBlockForever (the names should be changed)
need some hackery. The functionality for tryButDoNotSuspend
should already exist in the current implementation of putMVar,
whereas tryForaWhileButDoNotBlockForever looks a bit tricky.
tryButDoNotSuspend would be available as a more efficient special
instance of tryForaWhileButDoNotBlockForever, for those who
really want their non-blocking operations to time out
immediately.

So, I am not trying to decrease the efficiency of anyone's
applications, I am just wondering whether the current asymmetry
in take/put, the problems I listed in my previous email and some
of the recent proposals on this list could be used to come up
with a modified base interface for CH that would support both the
old and the new uses.

If this can form a useful basis for the ongoing discussion,
that's all I want right now (with a timeout of 0:-).

Claus




Reply via email to