Hi, I've been playing with some haskell bindings which basically bind C procedures in the IO monad. To write 'interesting' programs, you often need to manage your own state in addition to the implicit IO state. I have been allocating references with newIORef and then passing them around all my functions. But having to thread a parameter through all your functions is inconvenient (and fragile). I can see the solution to this problem in layering a state monad over the IO monad, and I can just about grok the syntax I need to use StateT, with a nice encapsulated environment record type and nice selector functions to use with gets. Now of course I don't even need to use IORefs any more since I can be more explicit and precise about my state and how it's manipulated. (this is potential even a useful feature, since I can step back to earlier time points just by saving the state, etc.) Unfortunately, it's not going to work. It's not going to work because some of the procedures take callbacks, and the callbacks are values of type IO (). I can see two solutions to this: a) revert to using an IORef, and use lexical scoping to define my callbacks in a location such that the reference to the environment is in scope. This will work, but it's just not convenient to define all your functions inside another function so you can do a lexical scoping trick... b) write the callbacks as values of type StateT Env IO () and then use the following 'environment binder' bindEnv env s = do { x <- readIORef env ; s' <- execStateT s x; writeIORef env s'} when I pass them to a callback. (which works fine as long as I have used lexical scoping to make sure env is visible at the binding site) Is this the right approach? Is there a better one? From the library perspective, would it be cool if library callback routines, which currently have a type like addCallback :: IO() -> IO() could be given the type addCallback :: MonadIO() -> IO() So that they'll let you add callbacks for any monad layered over IO. Or doesn't this idea work? Jules
Jules Bean wrote:
[...] Unfortunately, it's not going to work. It's not going to work because some of the procedures take callbacks, and the callbacks are values of type IO (). I can see two solutions to this:
a) revert to using an IORef [...] b) write the callbacks as values of type StateT Env IO () [...]
or c) Give up any hope of clean semantics and simply use a common hack like: {-# NOINLINE myGlobalVar #-} myGlobalVar :: IORef Int myGlobalVar = unsafePerformIO (newIORef 0) My GLUT binding does it happily, as does GHC itself, just look for NOINLINE pragmas in the following files: :-] http://cvs.haskell.org/cgi-bin/cvsweb.cgi/fptools/libraries/GLUT/Graphics/UI... http://cvs.haskell.org/cgi-bin/cvsweb.cgi/fptools/libraries/GLUT/Graphics/UI... http://cvs.haskell.org/cgi-bin/cvsweb.cgi/fptools/ghc/compiler/HsVersions.h?... Cheers, S.
participants (2)
-
Jules Bean -
Sven Panne