Making `Async` a functor (parconc)

In the book, Simon Marlow explains that

data Async a = Async ThreadId (TMVar (Either SomeException a))

can not be made a functor, because (p. 202):

The type of the result that the Async will place in the TMVar is fixed when we create the Async.

So he changes the definition to:

data Async a = Async ThreadId (STM (Either SomeException a))

Could someone elaborate on, or explain in other terms, why the first definition isn’t suitable?

Edit: I suppose it is because TMVar is surprisingly an instance of only Eq…

1 Like

I think it will turn out to be less surprising if you try to write a function
fmapTVar :: (a -> b) -> TMVar a -> TMVar b. The problem is that TMVar itself lives in some global state, i.e. the STM Monad. We cannot map over it without modifying that state, too. So what you end up doing is just to use the state that the variable lives in.

4 Likes

A mutable reference cannot safely support a type-changing map operation in a non-linear context.

In the case that the implementation does not copy, the issue is that the original reference you mapped over still exists at the original type, and can still be written to or read from. Suppose we have:

mapIORef :: (a -> b) -> IORef a -> IORef b

Then:

unsafeCoerceIO :: a -> IO b
unsafeCoerceIO x = do
  refb <- newIORef undefined
  let refa = mapIORef (const x) refb
  readIORef refb

In the case that the implementation does copy, though we can no longer unsafely coerce, there are still two issues: purity and law. The Functor-compatible signature I gave above for mapIORef cannot satisfy the Functor law fmap id = id if it copies. Worse, it’s inherently impure: mapIORef f x /= mapIORef f x. You can resolve the matter of impurity by changing the signature to (a -> b) -> IORef a -> IO (IORef b), but that’s no fmap.

With some minor translation, this applies equally to MVar and TMVar.

1 Like

Fantastic answers, what a great read. What I take is that the issue is really that IORefs et al. are truly mutable, so Simon shifts focus from directly handling mutable state to mapping over a computation (and such a mapping can be pure - fmap)!

Changing the TMVar to STM feels like Coyoneda in different clothes :cowboy_hat_face:

3 Likes