Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 222
vondip Avatar asked Jun 22 '15 13:06

vondip


Video Answer


2 Answers

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.

like image 151
Benjamin Gruenbaum Avatar answered Sep 27 '22 20:09

Benjamin Gruenbaum


This is more an extended comment - it doesn't address the main question, but your implementation of shufffle. There are 2 issues with it:

  1. Your implementation is inefficient - O(n^2).
  2. IO isn't the right type for it - shuffle has no general side effects, it just needs a source of randomness.

For (1) there are several solutions: One is to use Seq and its index, which is O(log n), which would make shuffle O(n log n). Or you could use ST arrays and one of the standard algorithms to get O(n).

For (2), all you need is threading a random generator, not full power of IO. There is already nice library MonadRandom that defines a monad (and a type-class) for randomized computations. And another package already provides the shuffle function. Since IO is an instance of MonadRandom, you can just use shuffle directly as a replacement for your function.

like image 37
Petr Avatar answered Sep 27 '22 20:09

Petr