Looking for a more functional way to do this

Working with HOpenGL and GLUT, I find myself approaching a common problem with a common solution that I don't really like all that much, as it reeks of procedural programming. Basically the problem is that of complex program state, such that when the user provides input to the program in the form of a mouse click or a typed string or character, the program updates its internal state to reflect this, whether that's changing the rotation, scale, or position of a screen element, or deciding what data to have loaded from disc. What I often do is something that looks like this: data ProgramState = ProgramState { some_associative_data :: Map String String , position :: GL.Vector3 Float , look_at :: GL Vector3 Float , selectables :: Map GLuint NamedObject } render :: IORef ProgramState -> IO () render state = do st <- readIORef state ... handleMouseClicks :: IORef ProgramState -> GLUT.KeyboardMouseHandler handleMouseClicks state ... = do st <- readIORef state ... main = do ... let st = ProgramState { Map.empty ... } render' = render st mouse' = handleMouseClicks st GLUT.renderCallback $= render GLUT.keyboardMouseCallback $= Just mouse' and so on and so forth. Generally there are not fewer than 5 and not more than about 32 variables that I have to track between mouse clicks, and there's all this boilerplate code as well. I'm searching for a better way to do this, and I feel sure there is one. I'm considering using Template Haskell or possibly SYB to generate this code, but it also seems like I also ought to be able to declare some kind of state monad or continuation monad that can encapsulate ProgramState without having to declare an explicit structure for it everytime. For one thing, I'd like to genericize this code and write something akin to Processing for Haskell (http://www.processing.org).

There is the State Monad which is build just for that kind of purpose, I believe: http://www.haskell.org/all_about_monads/html/statemonad.html That would safe you from passing around the State Jefferson Heard schrieb:
Working with HOpenGL and GLUT, I find myself approaching a common problem with a common solution that I don't really like all that much, as it reeks of procedural programming. Basically the problem is that of complex program state, such that when the user provides input to the program in the form of a mouse click or a typed string or character, the program updates its internal state to reflect this, whether that's changing the rotation, scale, or position of a screen element, or deciding what data to have loaded from disc.
What I often do is something that looks like this:
data ProgramState = ProgramState { some_associative_data :: Map String String , position :: GL.Vector3 Float , look_at :: GL Vector3 Float , selectables :: Map GLuint NamedObject }
render :: IORef ProgramState -> IO () render state = do st <- readIORef state ...
handleMouseClicks :: IORef ProgramState -> GLUT.KeyboardMouseHandler handleMouseClicks state ... = do st <- readIORef state ...
main = do ... let st = ProgramState { Map.empty ... } render' = render st mouse' = handleMouseClicks st
GLUT.renderCallback $= render GLUT.keyboardMouseCallback $= Just mouse'
and so on and so forth. Generally there are not fewer than 5 and not more than about 32 variables that I have to track between mouse clicks, and there's all this boilerplate code as well. I'm searching for a better way to do this, and I feel sure there is one. I'm considering using Template Haskell or possibly SYB to generate this code, but it also seems like I also ought to be able to declare some kind of state monad or continuation monad that can encapsulate ProgramState without having to declare an explicit structure for it everytime.
For one thing, I'd like to genericize this code and write something akin to Processing for Haskell (http://www.processing.org). _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

Adrian, my understanding is that it's not that simple, because I need
to preserve the state between calls to GLUT's callbacks, which all
return IO ().
2008/8/6 Adrian Neumann
There is the State Monad which is build just for that kind of purpose, I believe:
http://www.haskell.org/all_about_monads/html/statemonad.html
That would safe you from passing around the State
Jefferson Heard schrieb:
Working with HOpenGL and GLUT, I find myself approaching a common problem with a common solution that I don't really like all that much, as it reeks of procedural programming. Basically the problem is that of complex program state, such that when the user provides input to the program in the form of a mouse click or a typed string or character, the program updates its internal state to reflect this, whether that's changing the rotation, scale, or position of a screen element, or deciding what data to have loaded from disc.
What I often do is something that looks like this:
data ProgramState = ProgramState { some_associative_data :: Map String String , position :: GL.Vector3 Float , look_at :: GL Vector3 Float , selectables :: Map GLuint NamedObject }
render :: IORef ProgramState -> IO () render state = do st <- readIORef state ...
handleMouseClicks :: IORef ProgramState -> GLUT.KeyboardMouseHandler handleMouseClicks state ... = do st <- readIORef state ...
main = do ... let st = ProgramState { Map.empty ... } render' = render st mouse' = handleMouseClicks st
GLUT.renderCallback $= render GLUT.keyboardMouseCallback $= Just mouse'
and so on and so forth. Generally there are not fewer than 5 and not more than about 32 variables that I have to track between mouse clicks, and there's all this boilerplate code as well. I'm searching for a better way to do this, and I feel sure there is one. I'm considering using Template Haskell or possibly SYB to generate this code, but it also seems like I also ought to be able to declare some kind of state monad or continuation monad that can encapsulate ProgramState without having to declare an explicit structure for it everytime.
For one thing, I'd like to genericize this code and write something akin to Processing for Haskell (http://www.processing.org). _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe
-- I try to take things like a crow; war and chaos don't always ruin a picnic, they just mean you have to be careful what you swallow. -- Jessica Edwards

Jefferson Heard wrote:
Adrian, my understanding is that it's not that simple, because I need to preserve the state between calls to GLUT's callbacks, which all return IO ().
Then maybe see: http://www.haskell.org/pipermail/haskell-cafe/2007-July/028501.html
2008/8/6 Adrian Neumann
: There is the State Monad which is build just for that kind of purpose, I believe:
http://www.haskell.org/all_about_monads/html/statemonad.html
That would safe you from passing around the State
Jefferson Heard schrieb:
Working with HOpenGL and GLUT,
[snip] Claude

Jefferson Heard wrote:
Adrian, my understanding is that it's not that simple, because I need to preserve the state between calls to GLUT's callbacks, which all return IO ().
2008/8/6 Adrian Neumann
: There is the State Monad...
data ProgramState = ProgramState { some_associative_data :: Map String String , position :: GL.Vector3 Float , look_at :: GL Vector3 Float , selectables :: Map GLuint NamedObject }
render :: IORef ProgramState -> IO ()
You might find it easier to think in terms of a Reader monad where each component of your ProgramState above is now a separate IORef. Then you can just use a function: mkCallback :: ReaderT ProgramStateRefs IO () -> IO () to create the necessary callbacks for GLUT, and there is no need to interleave any state between calls (since it's all kept in the IO monad directly). Eg: data ProgramStateRefs = ProgramStateRefs { some_associative_data :: IORef (Map String String) , ... } main = do r <- createProgramStateRefs let mkCallback :: ReaderT ProgramStateRefs IO a -> IO a mkCallback (ReaderT r_ma) = r_ma r GLUT.renderCallback $= mkCallback onRender ... onRender :: ReaderT ProgramStateRefs IO () onRender = do ... You can then go further and use phantom types to build specialized monads by newtyping the (ReaderT ProgramStateRefs IO) to limit the operations possible in each callback (e.g. to prevent calls to rendering methods inside a keyboard handler etc) though at some point there is a tradeoff between how much you really need to enforce statically and how much time you have to devise suitable schemes of phantom type parameters to enforce it. (Disclaimer: the above code is untested and may contain errors ;-) ) Regards, Brian.
participants (4)
-
Adrian Neumann
-
Brian Hulley
-
Claude Heiland-Allen
-
Jefferson Heard