Global variables via unsafePerformIO in Haskell
Asked Answered
W

2

3

The GHC API requires that some initialisation occurs before invocation. Specifically, parseStaticFlags can only be called once.

I have functions that can call runGhc :: MaybeFilePath :: Ghc a -> IO a multiple times to run some GHC API methods. However, some of that initialisation should only occur the first time that function is called.

I seem to remember from Yi source that it is possible to create a global variable something like

ghcInitialised :: MVar (Bool,[String])
ghcInitialised = unsafePerformIO $ newMVar (False,[])

so that in the monadic action that calls runGhc we can have

(init,flags) <- readMVar ghcInitialised
when (not init) $ do
   ...
   (_,_,staticFlagWarnings) <- parseStaticFlags ...
   ...
   putMVar ghcInitialised (True,staticFlagWarnings)

However, I can not recall exactly how it is done. This code is in the runMonad function for the monad that wraps a GhcMonad. I am well aware that using unsafePerformIO is not pure or functional, but (at the time) this was the best way of achieving a practical result.

[Edit: the working solution:

{-# NOINLINE ghcInitialised #-}
ghcInitialised :: MVar (Bool,[String])
ghcInitialised = unsafePerformIO $ newMVar (False,[])

so that in the monadic action that calls runGhc we can have

(init,flags) <- takeMVar ghcInitialised
when (not init) $ do
   ...
   (_,_,staticFlagWarnings) <- parseStaticFlags ...
   ...
   putMVar ghcInitialised (True,staticFlagWarnings)
Walcott answered 27/6, 2011 at 4:52 Comment(0)
E
1

See this answer. It shows how to use a global counter that 'ticks' everytime you look at it. You don't need a counter, but instead of +1, you just put True into it.

Or, even better, you put the initialisation code into the unsafePerformIO, (guarded by an if of course).

Epicene answered 27/6, 2011 at 8:40 Comment(6)
Ah, I should have used IORef not MVar.Walcott
No, MVar is good. MVar is in fact better, since it works even with concurrency.Maida
My program was hanging when I used MVars.Walcott
Your readMVar should be a takeMVar, otherwise putMVar will block.Maida
But beware, if you switch to a takeMVar you'll need a putMVar to match it even when it's already initialized.Maida
Thanks @ShiDoiShi and @augustss, my code works with takeMVar and putMVar (as well as with IORefs).Walcott
P
2

You need to turn inlining off. The other important thing: The type has to be monomorph (= no type variables), otherwise you might evaluate unsafePerformIO for each actual type.

{-# NOINLINE ghcInitialised #-}
ghcInitialised :: MVar (Bool,[String])
ghcInitialised = unsafePerformIO $ newMVar (False,[])
Peshitta answered 28/6, 2011 at 13:48 Comment(0)
E
1

See this answer. It shows how to use a global counter that 'ticks' everytime you look at it. You don't need a counter, but instead of +1, you just put True into it.

Or, even better, you put the initialisation code into the unsafePerformIO, (guarded by an if of course).

Epicene answered 27/6, 2011 at 8:40 Comment(6)
Ah, I should have used IORef not MVar.Walcott
No, MVar is good. MVar is in fact better, since it works even with concurrency.Maida
My program was hanging when I used MVars.Walcott
Your readMVar should be a takeMVar, otherwise putMVar will block.Maida
But beware, if you switch to a takeMVar you'll need a putMVar to match it even when it's already initialized.Maida
Thanks @ShiDoiShi and @augustss, my code works with takeMVar and putMVar (as well as with IORefs).Walcott

© 2022 - 2024 — McMap. All rights reserved.