This is really a sentence from the August comment. Carefully untested, but this will get you started:
import Control.Applicative import Control.Monad import Control.Monad.Trans import Control.Monad.Trans.Reader import Graphics.UI.Gtk import Graphics.UI.Gtk.Glade getButton :: String -> ReaderT GladeXML IO Button getButton buttonName = do gladeXML <- ask return . lift $ xmlGetWidget gladeXML castToButton buttonName
To perform the ReaderT GladeXML IO action:
Try reading the docs on Control.Monad.Trans.Reader and some monad transformer tutorials.
Let me try again. What I'm doing brings together two ideas that you can solve separately, and then combine again:
- Monad
Reader - Monad Transformers
You can start by reading to try to understand the monad Reader :
Basically, Reader is a monad that builds values that depend on the missing, implicit value of the "environment". The Reader monad has an action called ask :: Reader rr , the result of which is the environment value.
So, the idea is that wherever there is GladeXML -> something , you can rewrite this function in a monadic action like Reader GladeXML something . So, for example, the simplification of my example above (without transformer monad):
getButton :: String -> Reader GladeXML (IO Button) getButton buttonName = do -- The variable gladeXML gets the value of the "implicit" GladeXML value gladeXML <- ask -- Now we use that value as an argument to the xmlGetWidget function. return $ xmlGetWidget gladeXML castToButton buttonName
The way you use Reader is then found with the function runReader :: Reader ra -> r -> a . Schematically:
{- NOTE: none of this is guaranteed to even compile... -} example :: IO Button example = do _ <- initGUI -- Setup Just xml <- loadGladeFile "tutorial.glade" runReader (getButton "button1") xml
However, since you use both Reader and IO here, you must make a unified monad that has the "powers" of both. That monad transformers add to the picture. A ReaderT GladeXML IO a is a conceptual IO action that has access to the "implicit" value of GladeXML:
getButton :: String -> ReaderT GladeXML IO Button getButton buttonName = do gladeXML <- ask -- There is one catch: to use any IO action, you have to prefix it with -- the `lift` function... button <- lift $ xmlGetWidget gladeXML castToButton buttonName return button -- I've refactored this slightly to *not* take a list of actions. onButtonClick :: String -> ReaderT GladeXML IO a -> ReaderT GladeXML IO () onButtonClick gladeXML buttonName action = do aButton <- getButton buttonName xml <- ask _ <- lift $ onClicked aButton (runReaderT action xml) return () -- This is the piece of code that illustrates the payoff of the refactoring. -- Note how there is no variable being passed around for the xml. This is -- because I'm making a "big" ReaderT action out of small ones, and they will -- all implicitly get the same `GladeXML` value threaded through them. makeButton1 :: ReaderT GladeXML IO Button makeButton1 = do button1 <- getButton "button1" onButtonClick "button1" $ do lift $ putStrLn "Hello, world" return button1 -- The `main` action just fetches the `GladeXML` value and hands it off to the -- actual main logic, which is a `ReaderT` that expects that `GladeXML` main :: IO () main = do xml <- ... runReaderT actualMain xml actualMain :: ReaderT GladeXML IO () actualMain = do ...