如何减少在Haskell中传递的参数数量?

时间:2014-06-24 21:06:47

标签: haskell gtk glade

我在Haskell中慢慢加快速度,尝试使用gui toolgit等等。我按照使用glade的基本教程创建了一个简单的GUI应用程序,现在我试图尝试模块化它。特别是,我想利用函数而不是在main中执行所有操作。我做的第一件事是创建单独的函数来访问按钮和关联单击按钮时要执行的代码。它工作正常,但如果你看下面的代码,我必须携带整个glade XML"变量"和我在一起。我意识到我们不会在Haskell中做全局,但在我看来,必须有一个更好的机制,而不是在函数中携带每个变量。显然在OO世界中,XML的东西只是一个类中的实例变量,因此在任何地方都可以隐式使用。什么是"对"在Haskell世界中这样做的方法?

  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

1 个答案:

答案 0 :(得分:11)

这真是奥古斯都的建议。评论。彻底未经测试,但这会让你开始:

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

运行ReaderT GladeXML IO操作:

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

尝试阅读Control.Monad.Trans.Reader上的文档和一些monad变换器教程。


让我再试一次。我正在做的是将两个你可以单独解决的想法结合起来,然后重新组合起来:

  1. Reader monad
  2. Monad变形金刚
  3. 您可以从阅读这些内容开始,尝试了解Reader monad:

    基本上,Reader是一个monad,它构造依赖于缺失的,隐含的&#34;环境的值。值。在Reader monad中,有一个名为ask :: Reader r r的操作,其结果是环境值。

    所以我的想法是,无论你有GladeXML -> something,你都可以将该函数重写为Reader GladeXML something类型的monadic动作。例如,简化上面的例子(没有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
    

    您使用Reader的方式是使用runReader :: Reader r a -> r -> a功能。示意性地:

    {- 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
    

    但是,由于您在此处同时使用ReaderIO,因此您要做的就是制作一个具有&#34; power&#34;的组合monad。两者。这是monad变形金刚添加到图片中的内容。概念上,ReaderT GladeXML IO aIO操作,可以访问&#34;隐含&#34; 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 ...