How to reduce the number of arguments I need to pass to Haskell?

I am really slow in Haskell trying to use the gui toolgit utility etc. I followed the basic guide on using glade to create a simple graphical application, and now I'm trying to modulate it. In particular, I wanted to use functions instead of doing everything basically. The first thing I did was to create separate functions for accessing the buttons and linking the code that will be executed when the buttons are clicked. It works fine, but if you look at the code below, I will have to wrap the whole XML variable "variable" around me. I understand that we are not doing global in Haskell, but it seems to me that there should be a better mechanism, and not wrap each variable around functions. Obviously, in the OO world, XML will be just an instance variable in a class that is implicitly accessible everywhere. What is the “right” way to do this in the Haskell world?

module Main (main) where import Graphics.UI.Gtk import Graphics.UI.Gtk.Glade getButton :: GladeXML -> String -> IO Button getButton gladeXML buttonName = xmlGetWidget gladeXML castToButton buttonName onButtonClick :: GladeXML -> String -> [IO a] -> IO () onButtonClick gladeXML buttonName codeSequence = do aButton <- getButton gladeXML buttonName _ <- onClicked aButton $ do -- Run the sequence of operations when user clicks sequence_ codeSequence return () loadGladeFile :: FilePath -> IO (Maybe GladeXML) loadGladeFile filename = do g <- xmlNew filename return g main :: IO () main = do _ <- initGUI -- Setup -- Load the Glade XML file Just xml <- loadGladeFile "tutorial.glade" -- Create main window (everything inside will be created too) window <- xmlGetWidget xml castToWindow "window1" -- Define what to do when we quit _ <- onDestroy window mainQuit -- Show the wondow widgetShowAll window -- Associate an onClick event with a button onButtonClick xml "button1" [putStrLn "Hello, world"] -- Off we go mainGUI 
+7
haskell gtk glade
source share
1 answer

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:

 -- Well, you should probably just use `runReaderT` directly, but at least the -- type signature here is instructive. runGladeXMLReader :: ReaderT GladeXML IO a -> GladeXML -> IO a runGladeXMLReader = runReaderT 

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 ... 
+11
source share

All Articles