【问题标题】:How can I reduce the number of arguments I have to pass around in Haskell?如何减少必须在 Haskell 中传递的参数数量?
【发布时间】:2014-08-15 06:52:13
【问题描述】:

我在 Haskell 中的速度非常缓慢,试图获得一个可用的 gui 工具等。我遵循了关于使用 glade 创建一个简单的 GUI 应用程序的基本教程,现在我正在尝试对其进行模块化。特别是,我想利用函数而不是在 main.js 中做所有事情。我做的第一件事是创建单独的函数来访问按钮并关联单击按钮时要执行的代码。它工作正常,但如果您查看下面的代码,我必须随身携带整个 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

【问题讨论】:

  • 您可以将所需的所有内容都粘贴到记录中并传递。或者使用 reader monad 来避免传递它。
  • @David 如果您确实使用唱片,您可能想试一试 Jon Sterling 的黑胶唱片 (jonmsterling.com/posts/…)。我自己没有尝试过,但听说他们解决了记录的命名空间问题 (ghc.haskell.org/trac/ghc/wiki/Records)
  • 我仍在尝试围绕基础知识。到目前为止,我最大的问题是在 Haskell 中实现过去对我来说微不足道的事情(使用 Delphi、C++、Python、Objective-C、Smalltalk 等语言)似乎非常复杂。虽然我开始从概念上理解这个 Reader Monad 发生了什么,但它似乎是解决一个非常简单问题的一种非常复杂的方法。绕过函数式编程的“纯粹性”所需的机制非常复杂。

标签: haskell gtk glade


【解决方案1】:

这确实是来自 augustss 评论的建议。完全未经测试,但这会让你开始:

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 转换器

您可以从阅读这些内容开始尝试理解 Reader monad:

基本上,Reader 是一个 monad,它构造依赖于缺失的隐式“环境”值的值。在Reader monad 中有一个名为ask :: Reader r r 的动作,其结果是环境值。

所以这个想法是,无论你在哪里拥有GladeXML -&gt; something,你都可以将该函数重写为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

您使用Reader 的方式是使用runReader :: Reader r a -&gt; r -&gt; 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,因此您要做的是创建一个具有两者“权力”的组合 monad。这就是monad转换器添加到图片中的内容。从概念上讲,ReaderT GladeXML IO 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 ...

【讨论】:

  • 如果我理解这一点,我相信我会立即将您的答案标记为正确。 ?
  • 我还不够好,无法直接从文档中理解这些内容。我发现文档几乎需要一个已经是专家的人。我会尝试在阅读器上找到一些教程。感谢您抽出宝贵时间回复。
  • getButton (say) 函数是如何被调用的?我的困难(我敢打赌其他人也一样)是库和文档通常定义了这些东西,但实际上并没有向您展示如何实际使用它们。这从一开始就是我的主要问题。
  • @David 我已经扩展了答案。看看这是否有帮助。恐怕我做的一切都有些匆忙。
  • 也可以使用ReaderT GladeXML IO 的类型别名来使类型签名更具可读性。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-06
  • 2021-12-02
  • 2019-01-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多