Haskell GUI 编程和懒惰评估的问题
Trouble with Haskell GUI programming and lazy evaluation
在尝试将 Haskell 用于图形应用程序时,我在急切地让 GUI 求值时遇到了很多麻烦。
例如,我有时会尝试在程序的某处创建小部件,将其打包并发送到我程序的其他组件。
这不是很好,通常不会导致小部件显示,因为惰性评估会在小部件有机会出现在屏幕上之前丢弃小部件。
所以我想知道,除了放入一堆 seq
和其他变体来鼓励急切的评估之外,can/how 是否有人处理这个问题?
我已经搜索了答案,但找不到与这行问题相关的任何内容。
编辑:示例代码
下面的代码生成了一个空 window。
{-# LANGUAGE RecursiveDo #-}
-- allows recursive do notation
-- mdo
-- ...
import Control.Monad
import Control.Monad.IO.Class
import qualified Data.Map.Strict as Map
import qualified Data.List as List
import System.Random
import Graphics.UI.WX hiding (Event)
import Graphics.UI.WXCore as WXCore
import Reactive.Banana
import Reactive.Banana.WX
boardWidth, boardHeight :: Int
boardWidth = 41
boardHeight = 81
main :: IO ()
main = start tetris
tetris = do
ff <- frame [text := "Tetris"
,bgcolor := white
,resizeable:= False]
p <- panel ff []
set ff [ layout := minsize (sz 100 100) $ widget p]
pps <- return $ Map.fromList $ map (\l@(x,y) -> (l, button p []))
[(x,y) | x <- [1..(boardWidth `div` 2)], y <- [1..(boardHeight `div` 2)]]
-- p <- pps Map.! (1,2)
d <- return $ map (\(x,y_m) -> y_m >>= (\y -> set ff [ color := white, layout := minsize (sz 300 300) $ widget y ])) $ Map.toList pps
-- let networkDescription :: Moment IO ()
return ff
第 p <- pps Map.! (1,2)
行导致 1、2 处的图块显示的尺寸不正确。
This doesn't work out that well, usually never resulting in the widget getting displayed due to lazy evaluation discarding the widget before it gets a chance to be put on the screen.
正如 Thomas M. DuBuisson 所建议的那样,这是一个误诊:这里的问题与惰性求值无关(而且在任何情况下,惰性求值都不会丢弃您实际尝试使用的值)。让我们看一下您应该将按钮添加到面板的那一行:
d <- return $ map (\(x,y_m) ->
y_m >>= (\y -> set ff [ color := white, layout := minsize (sz 300 300) $ widget y ]))
$ Map.toList pps
Map.toList pps
的类型是[((Int, Int), IO (Button ())]
。您对 map
的使用将其更改为 [IO ()]
(参见 the type of set
),一个包含 IO
操作的 列表 。实际上,您正在设置添加按钮的操作,但实际上您从未 运行 它们。为此,您需要 Data.Foldable
中的 traverse_
而不是 map
:
traverse_ (\(x,y_m) ->
y_m >>= (\y -> set ff [ color := white, layout := minsize (sz 300 300) $ widget y ]))
$ Map.toList pps
(有关 traverse_
及其同类 traverse
的更多信息,请参阅 this answer,以及对父问题的其他答案。)
请注意,除非我严重误读了 WX-specific 代码,否则 这仍然无法满足您的要求。由于您为列表元素生成的每个 IO
操作都会重置框架的布局,因此您最终只会得到框架中的最后一个按钮。要获得合理的布局,您必须实际使用坐标值以及 WX 布局组合器来设置包含所有按钮的单一布局。比照。 the Graphics.UI.WX.Layout
documentation.
最后一点,在 do-block...
中编写这样的代码
bar <- return foo
... 总是多余的:它可以替换为:
let bar = foo
在你的情况下,这样做会更明显地表明你实际上并没有 运行执行这些操作。
(您对 Map.fromList
和 Map.toList
的使用也是多余的,但我想您已经怀疑了。)
在尝试将 Haskell 用于图形应用程序时,我在急切地让 GUI 求值时遇到了很多麻烦。
例如,我有时会尝试在程序的某处创建小部件,将其打包并发送到我程序的其他组件。
这不是很好,通常不会导致小部件显示,因为惰性评估会在小部件有机会出现在屏幕上之前丢弃小部件。
所以我想知道,除了放入一堆 seq
和其他变体来鼓励急切的评估之外,can/how 是否有人处理这个问题?
我已经搜索了答案,但找不到与这行问题相关的任何内容。
编辑:示例代码
下面的代码生成了一个空 window。
{-# LANGUAGE RecursiveDo #-}
-- allows recursive do notation
-- mdo
-- ...
import Control.Monad
import Control.Monad.IO.Class
import qualified Data.Map.Strict as Map
import qualified Data.List as List
import System.Random
import Graphics.UI.WX hiding (Event)
import Graphics.UI.WXCore as WXCore
import Reactive.Banana
import Reactive.Banana.WX
boardWidth, boardHeight :: Int
boardWidth = 41
boardHeight = 81
main :: IO ()
main = start tetris
tetris = do
ff <- frame [text := "Tetris"
,bgcolor := white
,resizeable:= False]
p <- panel ff []
set ff [ layout := minsize (sz 100 100) $ widget p]
pps <- return $ Map.fromList $ map (\l@(x,y) -> (l, button p []))
[(x,y) | x <- [1..(boardWidth `div` 2)], y <- [1..(boardHeight `div` 2)]]
-- p <- pps Map.! (1,2)
d <- return $ map (\(x,y_m) -> y_m >>= (\y -> set ff [ color := white, layout := minsize (sz 300 300) $ widget y ])) $ Map.toList pps
-- let networkDescription :: Moment IO ()
return ff
第 p <- pps Map.! (1,2)
行导致 1、2 处的图块显示的尺寸不正确。
This doesn't work out that well, usually never resulting in the widget getting displayed due to lazy evaluation discarding the widget before it gets a chance to be put on the screen.
正如 Thomas M. DuBuisson 所建议的那样,这是一个误诊:这里的问题与惰性求值无关(而且在任何情况下,惰性求值都不会丢弃您实际尝试使用的值)。让我们看一下您应该将按钮添加到面板的那一行:
d <- return $ map (\(x,y_m) ->
y_m >>= (\y -> set ff [ color := white, layout := minsize (sz 300 300) $ widget y ]))
$ Map.toList pps
Map.toList pps
的类型是[((Int, Int), IO (Button ())]
。您对 map
的使用将其更改为 [IO ()]
(参见 the type of set
),一个包含 IO
操作的 列表 。实际上,您正在设置添加按钮的操作,但实际上您从未 运行 它们。为此,您需要 Data.Foldable
中的 traverse_
而不是 map
:
traverse_ (\(x,y_m) ->
y_m >>= (\y -> set ff [ color := white, layout := minsize (sz 300 300) $ widget y ]))
$ Map.toList pps
(有关 traverse_
及其同类 traverse
的更多信息,请参阅 this answer,以及对父问题的其他答案。)
请注意,除非我严重误读了 WX-specific 代码,否则 这仍然无法满足您的要求。由于您为列表元素生成的每个 IO
操作都会重置框架的布局,因此您最终只会得到框架中的最后一个按钮。要获得合理的布局,您必须实际使用坐标值以及 WX 布局组合器来设置包含所有按钮的单一布局。比照。 the Graphics.UI.WX.Layout
documentation.
最后一点,在 do-block...
中编写这样的代码bar <- return foo
... 总是多余的:它可以替换为:
let bar = foo
在你的情况下,这样做会更明显地表明你实际上并没有 运行执行这些操作。
(您对 Map.fromList
和 Map.toList
的使用也是多余的,但我想您已经怀疑了。)