Input / Output Reference (IORef)
(updated in 27/08/2001)

Previous | Index | Next

The Input / Output reference is not a subtopic of HOpenGL, but a Haskell subtopic. Besides this, I decided to include this lesson in the tutorial because of two things:

  • If you are new to Haskell, you are probably not used to IORefs. Perhaps you have never heard about them before.
  • IORefs can be very important in the development of applications with HOpenGL.

This subtopic is not going to be largely explored; I just want to make sure you have enough basis to develop you own application/games using them. If you are already used to IORefs, feel free to start the following lesson.


This lesson is very simple. Think about an IORef as being a way for your functional program to keep the value of a variable (this surely isn't the best way to explain IORefs, but it will fit our purposes).

Suppose you would like to move a square around the screen, using the arrow keys. Your program will need to know the previous position of the square before moving it to the new position. The current position of the square, as you may have thought, will be stored in an IORef of type Float. The first thing you'll need to do is to create two new IORefs (one for the X position and the other for the Y position). This is done by the following code:

	pX <- newIORef 0.0
	pY <- newIORef 0.0

Here, pX and pY are the name of our IORefs and, because we initialized them with the value 0.0, our program knows they are of type IORef Float. If we used the code:

	pB <- newIORef True

then we would create an IORef Bool instead.

Now think about this question: if you wish to move the square using the arrow keys of your keyboard, wich kind of callback procedure needs to know the current position of the square? Yes, that's right: it is the specialFunc. So let's declare our specialFunc callback procedure with a function named special, which will receive our two IORef Float:

	specialFunc (Just (special pX pY))

The signature of function special will be, of course:

	special :: IORef Float -> IORef Float -> SpecialAction

The way to directly work with IORefs is very simple. Two basic things need to be done:

  1. Read the value of the IORef using readIORef.
  2. Update the IORef with the changes applied to the old value, using writeIORef.

Let's apply this to our example. Suppose the left arrow (KeyRight) is pressed. In this case, we would need to increase the current X position of the square (by 0.2, for example). Then we would have the following pattern matching:

	special pX _ KeyRight _ = do
		posX <- readIORef pX
		writeIORef pX (posX + 0.2)

The following code is more elegant, and has the same effect:

	special pX _ KeyRight _ = readIORef pX >>= \posX -> writeIORef pX (posX + 0.2)

Now we are half done. We still need to check for other inputs to update the square position, and create a function to effectively draw the square. The function drawSquare, used in the mouse managment example of the previous lesson, will also be used for this purpose:

	drawSquare :: (GLfloat,GLfloat) -> IO ()
	drawSquare (x,y) = do
		clear [ColorBufferBit]
		color (Color3 1.0 1.0 1.0 :: Color3 GLfloat)
		beginEnd Quads $ mapM_ vertex [
			Vertex3   x         y         0.0,
			Vertex3  (x + 0.2)  y         0.0,
			Vertex3  (x + 0.2) (y + 0.2)  0.0,
			Vertex3   x        (y + 0.2) (0.0 :: GLfloat)]
	
		flush

We also need to force our program to redraw the window every time we update the square position. This is done by calling the postRedisplay function just after updating the coordinates. This function automatically calls the displayFunc callback procedure. The special function will look like this:

	special :: IORef Float -> IORef Float -> SpecialAction
	special pX _  KeyRight _ = readIORef pX >>= \posX -> writeIORef pX (posX + 0.2) >> postRedisplay
	special pX _  KeyLeft  _ = readIORef pX >>= \posX -> writeIORef pX (posX - 0.2) >> postRedisplay
	special _  pY KeyUp    _ = readIORef pY >>= \posY -> writeIORef pY (posY + 0.2) >> postRedisplay
	special _  pY KeyDown  _ = readIORef pY >>= \posY -> writeIORef pY (posY - 0.2) >> postRedisplay
	special _ _ _ _ = return ()

The last thing to be done is to create a function that will be the displayFunc callback procedure. Once again, let's call it display. This function will receive the two IORefs, read them, and finally call the function drawSquare. Remembering that you'll need to import module IOExts to use all the IORef stuff, here you have the final code:

	import GL
	import GLUT
	import IOExts
		
	myInit :: IO () 
	myInit = do
		clearColor (Color4 0.0 0.0 0.0 0.0)
		clear [ColorBufferBit]
		matrixMode Projection
		loadIdentity
		ortho 0.0 1.0 0.0 1.0 (-1.0) 1.0
	
	special :: IORef Float -> IORef Float -> SpecialAction
	special pX  _ KeyRight _ = readIORef pX >>= \posX -> writeIORef pX (posX + 0.2) >> postRedisplay
	special pX  _ KeyLeft  _ = readIORef pX >>= \posX -> writeIORef pX (posX - 0.2) >> postRedisplay
	special _  pY KeyUp    _ = readIORef pY >>= \posY -> writeIORef pY (posY + 0.2) >> postRedisplay
	special _  pY KeyDown  _ = readIORef pY >>= \posY -> writeIORef pY (posY - 0.2) >> postRedisplay
	special _ _ _ _ = return ()
		
	drawSquare :: (GLfloat,GLfloat) -> IO ()
	drawSquare (x,y) = do
		clear [ColorBufferBit]
		color (Color3 1.0 1.0 1.0 :: Color3 GLfloat)
		beginEnd Quads $ mapM_ vertex [
			Vertex3   x         y         0.0,
			Vertex3  (x + 0.2)  y         0.0,
			Vertex3  (x + 0.2) (y + 0.2)  0.0,
			Vertex3   x        (y + 0.2) (0.0 :: GLfloat)]
	
		flush
		
	display :: IORef Float -> IORef Float -> DisplayAction
	display pX pY = do
		clear [ColorBufferBit]
		posX <- readIORef pX
		posY <- readIORef pY
		drawSquare (posX, posY)
		flush
	
	main :: IO ()
	main = do
		GLUT.init Nothing
		createWindow "IORef" (return ()) [ Single, GLUT.Rgba ]
	               	(Just (WindowPosition 100 100))
	               	(Just (WindowSize     500 500))
		myInit
		pX <- newIORef 0.0
		pY <- newIORef 0.0
		displayFunc (display pX pY)
		specialFunc (Just (special pX pY))
		mainLoop

Please keep in mind that the main purpose of the IORefs here is to don't let the value of the square coordinates to be lost. If you understood this example well, you won't have any difficulties to understand the uses of IORefs in the following lessons.



Hints and Tips:
  • To avoid writing all readIORef/writeIORef routines every time you want to update an IORef, use the following updateIORef function:
    		updateIORef :: IORef a -> (a -> a) -> IO ()
    		updateIORef ref f = do
       			val <- readIORef ref
       			writeIORef ref (f val)
       	
    In other words, this code:
    		readIORef pX >>= \posX -> writeIORef pX (posX + 0.2)
       	
    can be replaced by the following one:
    		updateIORef pX (\s -> s + 0.2)
       	
  • If you can avoid the use of IORefs, do it! They are too much imperative, and you are working with a functional language, aren't you?


Downloads:
  • Source code of the IORef example you've learned in this module.


HOpenGL Tutorial - Andre W B Furtado
Input / Output Reference (IORef)
www.cin.ufpe.br/~haskell/hopengl/ioref.html
Last updated in 27/08/2001
Informatics Center (CIn) - UFPE
Recife - PE - Brazil