[Haskell-cafe] Stupid newbie question of the day: why is newMVar in the IO monad?

Niklas Haas haskell at nand.wakku.to
Thu Apr 17 16:06:40 UTC 2014


On Thu, 17 Apr 2014 10:14:14 -0400, Brian Hurt <bhurt at spnz.org> wrote:
> So, I've hit a problem, and I'm wondering why this is.  I want to write a
> function which returns a global monotonically incrementing int.  This
> *should* be easy- just put a MVar in a global name, then update it as
> necessary:
> 
> globalCounter :: MVar Integer
> globalCounter = undefined
> 
> genId :: IO Integer
> genId = modifyMVar (\i -> (i+1, i)) globalCounter
> 
> The problem with this is defining globalCounter- and that is because
> newMVar returns IO (MVar a), and not just MVar a.  Now, I can go:
> 
> globalCounter = unsafePerformIO $ newMVar 0
> 
> but I hate using unsafePerformIO.  And I don't want to pass around the
> reference itself- I need to be in the IO monad with a StateT transform on
> top for other reasons, I don't want to complicate things.  And even if I
> were, I would just pass the counter around instead of the reference.  But
> it just feels like Haskell is being gratuitously difficult here.
> 
> It's not just the name, it's the fact that creating the MVar is
> *explicitly* modifying the state of the world, which implies there is
> something more going on here than just allocating some memory.  As an
> example of what I mean, newSTRef returns ST s (STRef s a).  This is
> explicitly saying that the created STRef is only visible in the given
> thread.  This is necessary for the implementation of the STRef, which is a
> mutable variable with no transactional guarantees.  If it were visible from
> another thread, then it could be accessed from the other thread, creating a
> potential race condition.  That I understand.  But that isn't the case
> here- MVar's are explicitly designed to be accessed from multiple threads.
> 
> So then I thought that it was something specific with MVars- maybe they
> need to do an OS call to set them up or something.  OK, so let's try some
> alternatives.  Like STM.
> 
> Nope.  newTVar has return type STM (TVar a) and newTMVar returns STM (TMVar
> a).  Throw these into atomically, and I'm right back to where I started.
>  newIORef returns IO (IORef a).  And that's just a pointer store (I
> thought).
> 
> It's easier for me to believe that I'm missing something here, rather than
> that Haskell is just being gratuitously difficult.  But I honestly don't
> see what it is I'm missing.  Help?
> 
> Brian

You said you're already using a StateT for other reasons but there's no
problem with nesting StateT, you just have to lift a bit. I would
personally use that approach - define another monad transformer layer on
top of your IO to handle around the int state-passing. Perhaps, if you
desperately want to avoid using StateT Int, you could also use ReaderT
(MVar a) to pass around the function automatically.

Note: Going beyond the ReaderT approach, there are more approaches one
could use, for example a (?var :: MVar Int) implicit parameter, or
another constraint that is resolved at runtime like with edwardk's
reflection library.

And finally, unsafePerformIO on this is safe as long as it's monomorphic
and you don't inline it, ie.

globalCounter :: Mvar Integer
globalCounter = unsafePerformIO $ newMVar 0
{-# NOINLINE globalCounter #-}



More information about the Haskell-Cafe mailing list