如何减少Haskell中必须传递的参数数量?
我在Haskell中的学习速度非常缓慢,试图让gui工具Git可用,等等。我遵循了使用glade创建简单gui应用程序的基本教程,现在我正在尝试将其模块化。特别是,我想利用函数,而不是在主目录中做所有事情。我做的第一件事是创建单独的函数来访问按钮,并将按钮单击时要执行的代码关联起来。它工作得很好,但是如果你看下面的代码,我必须随身携带整个GladeXML“变量”。我意识到我们在Haskell中不做全局的,但在我看来,必须有一个更好的机制,而不是在函数中携带每一个变量。显然,在OO世界中,XML内容只是类中的一个实例变量,因此在任何地方都隐式可用。在哈斯克尔的世界里,什么是“正确”的方法如何减少Haskell中必须传递的参数数量?,haskell,gtk,glade,Haskell,Gtk,Glade,我在Haskell中的学习速度非常缓慢,试图让gui工具Git可用,等等。我遵循了使用glade创建简单gui应用程序的基本教程,现在我正在尝试将其模块化。特别是,我想利用函数,而不是在主目录中做所有事情。我做的第一件事是创建单独的函数来访问按钮,并将按钮单击时要执行的代码关联起来。它工作得很好,但是如果你看下面的代码,我必须随身携带整个GladeXML“变量”。我意识到我们在Haskell中不做全局的,但在我看来,必须有一个更好的机制,而不是在函数中携带每一个变量。显然,在OO世界中,XML内
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
主模块(Main),其中
导入Graphics.UI.Gtk
导入Graphics.UI.Gtk.Glade
getButton::GladeXML->String->IO按钮
getButton gladeXML buttonName=
xmlGetWidget gladeXML castToButton按钮名称
onButtonClick::GladeXML->String->[IO a]->IO()
onButtonClick gladeXML buttonName codeSequence=do
aButton这确实是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
试着阅读上面的文档和一些monad transformer教程
让我再试一次。我所做的是将两个可以单独处理的想法结合起来,然后再组合起来:
Reader
monad
单子变压器
您可以从阅读这些内容开始,尝试理解阅读器
单子:
- );关注关于
读者的部分(“未来价值的世界”)
基本上,Reader
是一个monad,它构造的值依赖于缺少的隐式“环境”值。在Reader
monad中有一个名为ask::Reader r
的操作,其结果是环境值
因此,我们的想法是,无论您在哪里拥有GladeXML->something
,您都可以将该函数重写为Reader GladeXML something
类型的一元操作。例如,上面我的示例的简化(没有monad transformer):
然而,由于您在这里同时使用Reader
和IO
,因此您要做的是制作一个具有这两种功能的组合单子。这就是《蒙纳德变形金刚》为这幅画增添的色彩。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::String->ReaderT GladeXML IO按钮
getButton按钮名称=
使用gladeXML,您可以将所需的所有内容粘贴到一个记录中,并将其传递给其他人。或者使用读卡器monad来避免传递这些信息。@David如果你真的使用了唱片,你可能会想让Jon Sterling的乙烯基唱片旋转一下()。我自己还没有试过,但我听说它们解决了记录的名称空间问题(),我仍在努力了解基本知识。到目前为止,我最大的问题是,历史上对我来说微不足道的东西(使用德尔福、C++、Python、Objto-C、SimalTalk等语言)在Haskell中实现起来似乎非常复杂。当我开始从概念上理解这个读者Monad的情况时,它似乎是解决一个非常简单问题的一个非常复杂的方法。绕过函数式编程的“纯粹性”所需要的机制是非常复杂的。如果我理解了这一点,我相信我会立即给你的答案打上正确的标记。我还不够好,不能直接从文档中理解这些东西。我发现这些文档非常需要一个已经成为专家的人。我将尝试查找一些关于该读者的教程。感谢您花时间回复。如何调用getButton(say)函数?我的困难(我打赌其他人也有同样的困难)在于,库和文档通常定义了这些东西,但实际上并没有向您展示如何实际使用它们。这从一开始就是我的主要问题。@David我已经扩展了答案。看看这是否有帮助。恐怕我做的每件事都有点匆忙。还可以为ReaderT GladeXML IO
使用类型别名,使类型签名更具可读性。
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
{- 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
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 ...