views:

159

answers:

4

I'm having a hard time finding out how to make something change every time the user interacts with my program. It's hard to explain so here's an example (Haskell + wxhaskell):

simulate :: Int -> Frame () -> IO ()
simulate qNr window = do
 fdata <- readFile "qarchive"
 case (split (listTake (split fdata '\n') qNr 0) '#') of
  (qst:a:b:c:coralt:answer:x) -> do
   -- GUI Controls Initialization (buttons,text,etc...) 
   nextButton <- button window [text := "Next Question ->", on command := set infoField [text := "Next Question"], position := pt 185 155]
   return ()

main :: IO ()
main = start gui

gui :: IO ()
gui = do
 window <- frame [text := "Question program", clientSize := sz 640 480]
 headerText <- staticText window [text := "Title Text", fontSize := 20, position := pt 5 5]
 simulate 0 window
 return ()

I want some widgets to change when the "Next Question" button is pressed. I want to change these widgets to some values I read from a file. How can I keep track of what the current question number is? I cannot actually increment questionNumber as a variable, since Haskell doesn't permit such things. I guess there's another way to do it.

Example:

Initialize GUI
Read data from file
If button is pressed, increment question number by 1 and change widgets.

How do you tackle this kind of problem in a functional manner?

+1  A: 

One way to do this in a functional language is to thread your state through your functions. Some of the functionality will probably be handled by monads, such as getting the keyboard state, so you won't have to handle that yourself. An example (more or less pseudocode):

someFunction :: SomeDataTypeThatRepresentsState -> a ... -> (SDTTRS, a...) (change to fit reality of course)
someFunction x y .... | x == WeHaveKeyboardInput = someFunction dowhatever.....
                      | someFunction dowhateverelse

That should give you an idea of how to get started.

EDIT:

I should have mentioned Monads more in depth. Monads are basically a way to hide all of that state passing. It's oversimplified, I know, but it gets you started. To read more about monads and handling state, follow this link: http://www.engr.mun.ca/~theo/Misc/haskell_and_monads.htm

ZachS
I'd correct that edit by saying that (one type of) Monads is used not so much to **hide** state, as to **manage** it -- i.e., to use it as needed, and to tuck it "away" so it "takes care of itself" when you don't.But I am a pedant-in-waiting...
BMeph
+5  A: 

The arguments to functions are your variables. As the user enters new values, pass those values to functions.

For example, a simple program that updates a value based on user input:

main = loop 0

loop n = do
    print n
    v <- read `fmap` getLine
    loop (n + v)

Note the recursive calls to 'loop' have a different value passed each time, based on what the user provided.

This is a fundamental way of thinking in functional programming -- what would be a local, mutable variable in a loop in an imperative program becomes a parameter to a recursive function in a functional program.

Don Stewart
+2  A: 

Unfortunately, since wxHaskell is an event-based framework, Don's and ZachS answers don't apply.

What you have to do here is to allocate a mutable variable, just like you would in an imperative language. WxHaskell offers the function variable for that. Here an (incomplete) example:

gui = do
        ...
        counter <- variable [value := 1 :: Int]  -- allocate mutable variable
        button  <- button window [ text       := "Count!"
                                 , on command := next counter button]
    where
    next counter button = do
        n <- get counter value         -- get its value
        set button  [text  := show n]
        set counter [value := n+1]     -- set its value

Note that wxHaskell comes with lots of example source code. In particular, wx/ImageViewer.hs features a mutable variable.

However, except for special situations like this, it is beneficial to avoid mutable variables like the plague. (In fact, they make a mess in wxHaskell, too, it's just that they are difficult to avoid here.) Alternatives include rethinking your code, accumulating parameters, using types of the form s -> (a,s) and the state monad.

Heinrich Apfelmus
A: 

You might also want to check out the simple-observer package on Hackage. (Disclosure: I'm the package maintainer.)

It's a Haskell implementation of the Observer design pattern (just the ticket for solving "make something change every time ..." problems in an event-based framework), using MVars for mutable state. There's a blog post discussing it further here

I created this package when I was faced with exactly the same problem as you.

gimboland