Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I reduce the number of arguments I have to pass around in Haskell?

Tags:

haskell

gtk

glade

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
like image 443
David Avatar asked Jun 24 '14 21:06

David


People also ask

How do you pass arguments in Haskell?

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.

What does full stop mean in Haskell?

It means function composition.

What does == mean in Haskell?

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.

What is cons operator in Haskell?

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:[] .

How to reduce the number of arguments in a function?

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:

How to avoid function arguments when passing multiple variables?

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.

What is the ideal argument number for a function?

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).

Is a function with zero arguments a good 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.


1 Answers

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:

  1. The Reader monad
  2. Monad transformers

You might start by reading these to try to understand the Reader monad:

  • Inside My World (Ode to Functor and Monad); focus on the section about Reader ("The world of future values")
  • Reader Monad Purpose
  • help with reader monad

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 ...
like image 193
Luis Casillas Avatar answered Nov 09 '22 15:11

Luis Casillas