On 25/03/2010 23:16, Bas van Dijk wrote:
On Thu, Mar 25, 2010 at 11:23 PM, Simon Marlow<marlo...@gmail.com>  wrote:
So I'm all for deprecating 'block' in favor of 'mask'. However what do
we call 'unblock'? 'unmask' maybe? However when we have:

mask $ mask $ unmask x

and these operations have the counting nesting levels semantics,
asynchronous exception will not be unmasked in 'x'. However I don't
currently know of a nicer alternative.

But that's the semantics you wanted, isn't it?  Am I missing something?

Yes I like the nesting semantics that Twan proposed.

But with regard to naming, I think the name 'unmask' is a bit
misleading because it doesn't unmask asynchronous exceptions. What it
does is remove a layer of masking so to speak. I think the names of
the functions should reflect the nesting or stacking behavior. Maybe
something like:

addMaskingLayer :: IO a ->  IO a
removeMaskingLayer :: IO a ->  IO a
nrOfMaskingLayers :: IO Int

However I do find those a bit long and ugly...

I've been thinking some more about this, and I have a new proposal.

I came to the conclusion that counting nesting layers doesn't solve the problem: the wormhole still exists in the form of nested unmasks. That is, a library function could always escape out of a masked context by writing

  unmask $ unmask $ unmask $ ...

enough times.

The functions blockedApply and blockedApply2 proposed by Bas van Dijk earlier solve this problem:

blockedApply :: IO a -> (IO a -> IO b) -> IO b
blockedApply a f = do
  b <- blocked
  if b
    then f a
    else block $ f $ unblock a

blockedApply2 :: (c -> IO a) -> ((c -> IO a) -> IO b) -> IO b
blockedApply2 g f = do
  b <- blocked
  if b
    then f g
    else block $ f $ unblock . g

but they are needlessly complicated, in my opinion. This offers the same functionality:

mask :: ((IO a -> IO a) -> IO b) -> IO b
mask io = do
  b <- blocked
  if b
     then io id
     else block $ io unblock

to be used like this:

a `finally` b =
  mask $ \restore -> do
    r <- restore a `onException` b
    b
    return r

So the property we want is that if I call a library function

  mask $ \_ -> call_library_function

then there's no way that the library function can unmask exceptions. If all they have access to is 'mask', then that's true.

It's possible to mis-use the API, e.g.

  getUnmask = mask return

but this is also possible using blockedApply, it's just a bit harder:

  getUnmask = do
    m <- newEmptyMVar
    f <- blockedApply (join $ takeMVar m) return
    return (\io -> putMVar m io >> f)

To prevent these kind of shennanigans would need a parametricity trick like the ST monad. I don't think it's a big problem that you can do this, as long as (a) we can explain why it's a bad idea in the docs, and (b) we can still give a semantics to it, which we can.

So in summary, my proposal for the API is:

  mask  :: ((IO a -> IO a) -> IO b) -> IO b
  -- as above

  mask_ :: IO a -> IO a
  mask_ io = mask $ \_ -> io

and additionally:

  nonInterruptibleMask  :: ((IO a -> IO a) -> IO b) -> IO b
  nonInterruptibleMask_ :: IO a -> IO a

which is just like mask/mask_, except that blocking operations (e.g. takeMVar) are not interruptible. Nesting mask inside nonInterruptibleMask has no effect. The new version of 'blocked' would be:

   data MaskingState = Unmasked
                     | MaskedInterruptible
                     | MaskedNonInterruptible

  getMaskingState :: IO MaskingState

Comments? I have a working implementation, just cleaning it up to make a patch.

Cheers,
        Simon

_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe

Reply via email to