haskellmonadsthreepenny-gui

Haskell do clause with multiple monad types


I'm using a graphic library in Haskell called Threepenny-GUI. In this library the main function returns a UI monad object. This causes me much headache as when I attempt to unpack IO values into local variables I receive errors complaining of different monad types.

Here's an example of my problem. This is a slightly modified version of the standard main function, as given by Threepenny-GUI's code example:

main :: IO ()
main = startGUI defaultConfig setup

setup :: Window -> UI ()
setup w = do

labelsAndValues <- shuffle [1..10]

shuffle :: [Int] -> IO [Int]
shuffle [] = return []
shuffle xs = do randomPosition <- getStdRandom (randomR (0, length xs - 1))
                let (left, (a:right)) = splitAt randomPosition xs
                fmap (a:) (shuffle (left ++ right))

Please notice the fifth line:

labelsAndValues <- shuffle [1..10]

Which returns the following error:

Couldn't match type ‘IO’ with ‘UI’
Expected type: UI [Int]
  Actual type: IO [Int]
In a stmt of a 'do' block: labelsAndValues <- shuffle [1 .. 10]

As to my question, how do I unpack the IO function using the standard arrow notation (<-), and keep on having these variables as IO () rather than UI (), so I can easily pass them on to other functions.

Currently, the only solution I found was to use liftIO, but this causes conversion to the UI monad type, while I actually want to keep on using the IO type.


Solution

  • A do block is for a specific type of monad, you can't just change the type in the middle.

    You can either transform the action or you can nest it inside the do. Most times transformations will be ready for you. You can, for instance have a nested do that works with io and then convert it only at the point of interaction.

    In your case, a liftIOLater function is offered to handle this for you by the ThreePennyUI package.

    liftIOLater :: IO () -> UI ()

    Schedule an IO action to be run later.

    In order to perform the converse conversion, you can use runUI:

    runUI :: Window -> UI a -> IO a

    Execute an UI action in a particular browser window. Also runs all scheduled IO action.