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.fromListMap.toList 的使用也是多余的,但我想您已经怀疑了。)