I'm very slowly getting up to speed in Haskell, trying to get a gui toolgit usable, etc. I followed a basic tutorial on using glade to create a simple GUI app and now I'm trying to modularize it. In particular, I wanted to leverage functions instead of doing everything in main. The first thing I did was create separate functions for accessing buttons and associating code to be executed when the buttons are clicked. It works fine but if you look at the code below, I have to carry the entire glade XML "variable" around with me. I realize we don't do global in Haskell but it seems to me there has to be a better mechanism rather than carrying every single variable around in functions. Obviously in the OO world, the XML stuff would just be an instance variable in a class so implicitly available everywhere. What's 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
You have to pass file1 etc. to runQuery like every other function argument: main = do (file1:file2:file3:_) <- getArgs checkdata command <- getLine runQuery file1 file2 file3 (words command) runQuery file1 file2 file3 ("queryname":parameter1:parameter2) = do ... Well that was simple.
It means function composition.
The == is an operator for comparing if two things are equal. It is quite normal haskell function with type "Eq a => a -> a -> Bool". The type tells that it works on every type of a value that implements Eq typeclass, so it is kind of overloaded.
The : operator is known as the "cons" operator and is used to prepend a head element to a list. So [] is a list and x:[] is prepending x to the empty list making a the list [x] . If you then cons y:[x] you end up with the list [y, x] which is the same as y:x:[] .
Another technique is the pattern mentioned earlier, the Parameter Object pattern. We can aggregate arguments that are within same context, and then create a plain object containing those arguments. In next example, I’m going to show how to use the Parameter Object to reduce arguments’ number using the sample function showed earlier:
Another technique is to make variables used by multiple functions a private member variable to avoid passing them, but that expands the scope of the variable, possibly such that it's open to functions that don't actually need it. Just looking for ways to minimise function arguments, having accepted the reasons why it's a good idea to do so.
Uncle Bob (Robert C. Martin), in the Clean Code book, explains that an ideal argument’s number for a function is zero (niladic function). Followed by one (monadic function) and closely by two arguments (dyadic function).
We can agree that a function with zero arguments is a good function, we don’t have to worry too much about it. The perfect world will be if all functions have zero arguments, but we know that’s not possible.
This is really the suggestion from augustss' comment. Thoroughly 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 run a 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 is combining two ideas that you can tackle separately, then put back together:
Reader
monadYou might start by reading these to try to understand the Reader
monad:
Reader
("The world of future values")Basically, Reader
is a monad that constructs values that depend on a missing, implicit "environment" value. Within the Reader
monad there is an action called ask :: Reader r r
whose result is the environment value.
So the idea is that everywhere you have GladeXML -> something
, you can rewrite that function into a monadic action of type Reader GladeXML something
. So for example, a simplification of my example above (no monad transformer):
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 a Reader
then is with the runReader :: Reader r a -> r -> a
function. 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're using both Reader
and IO
in here, what you want to do is make a combined monad that has the "powers" of both. That's what monad transformers add to the picture. A ReaderT GladeXML IO a
is, conceptually, an IO
action that has access to an "implicit" GladeXML value:
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 ...
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With