0

In an attempt to use Haskell for a graphical application, I've had a lot of trouble getting the GUI to evaluate in an eager fashion.

For example, I sometimes attempt to create widgets somewhere in the program, package it and send it to other components of my program.

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.

So I wanted to know, other than putting in a bunch of seq and other variations to encourage eager evaluation, can/how does one deal with this issue?

I've searched for an answer, but couldn't really find anything related to this line of questioning.

EDIT: sample code

The code below produced an empty 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

The line p <- pps Map.! (1,2) results in the tile at 1, 2 to display without the proper size.

AlterionX
  • 81
  • 1
  • 10
  • This sounds like a misdiagnosis but without any code we can't help. I suggest you make a MCVE. – Thomas M. DuBuisson Mar 31 '18 at 19:04
  • 1
    You say, "other than putting in a bunch of `seq`, how does one deal with this issue?". Have you verified that putting in a bunch of `seq` does, in fact, deal with this issue? – Daniel Wagner Mar 31 '18 at 20:52
  • 2
    I can't write a full answer right now; in any case, the main issue here is that you aren't actually running the `IO` actions that add the buttons to the frame. You probably want to use `traverse` on the list, instead of `map` and `return`. – duplode Apr 01 '18 at 15:55

1 Answers1

1

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.

As Thomas M. DuBuisson suggests, that is a misdiagnosis: the issue here doesn't have to do with lazy evaluation (and in any case lazy evaluation wouldn't discard a value you actually tried to use). Let's have a look at the line at which you'd supposedly add the buttons to the panel:

d <- return $ map (\(x,y_m) ->
    y_m >>= (\y -> set ff [ color := white, layout := minsize (sz 300 300) $ widget y ]))
    $ Map.toList pps

The type of Map.toList pps is [((Int, Int), IO (Button ())]. Your use of map changes it into [IO ()] (cf. the type of set), a list of IO actions. In effect, you are setting up the actions that add the buttons, but you never actually run them. To do that, you want traverse_ from Data.Foldable instead of map:

traverse_ (\(x,y_m) ->
    y_m >>= (\y -> set ff [ color := white, layout := minsize (sz 300 300) $ widget y ]))
    $ Map.toList pps

(For more on traverse_ and its cousin traverse, see this answer, as well as the other answers to the parent question.)

Note that, unless I'm badly misreading the WX-specific code, this still won't do what you want. As each IO action you generate for a list element resets the layout of the frame, you'll end up with only the last button in the frame. To get a sensible layout, you'll have to actually use the coordinate values, as well as the WX layout combinators to set up a single layout with all buttons. Cf. the Graphics.UI.WX.Layout documentation.

On a final note, code like this in a do-block...

bar <- return foo

... is always redundant: it can be replaced by:

let bar = foo

In your case, doing that would have made it much more obvious that you weren't actually running the actions.

(Your uses of Map.fromList and Map.toList are also redundant, but I guess you already suspected that.)

duplode
  • 33,731
  • 7
  • 79
  • 150