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

Dav*_*vid 7 gtk haskell 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
Run Code Online (Sandbox Code Playgroud)

Lui*_*las 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
Run Code Online (Sandbox Code Playgroud)

要运行某个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
Run Code Online (Sandbox Code Playgroud)

尝试阅读文档Control.Monad.Trans.Reader,以及一些monad变换器教程.


让我再尝试一次.我正在做的是结合两个你可以单独解决的想法,然后再把它们放在一起:

  1. Reader单子
  2. Monad变形金刚

你可以从阅读这些开始尝试理解Readermonad:

基本上,Readermonad是构造依赖于缺失的隐含"环境"值的值的monad.在Readermonad中有一个叫做的动作,ask :: Reader r r其结果就是环境值.

因此,我们的想法是,无论您拥有什么GladeXML -> something,您都可以将该函数重写为类型的monadic动作Reader GladeXML something.例如,上面我的例子的简化(没有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
Run Code Online (Sandbox Code Playgroud)

你使用a的方式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
Run Code Online (Sandbox Code Playgroud)

然而,由于您同时使用Reader,并IO在这里,你想要做的是做一个单子结合具有两者的"权力".这就是monad变形金刚添加到图片中的原因.ReaderT GladeXML IO a概念上,A 是IO可以访问"隐式"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 ...
Run Code Online (Sandbox Code Playgroud)