Haskell do clause with multiple monad types
Asked Answered
L

3

8

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.

Leboeuf answered 22/6, 2015 at 13:20 Comment(4)
You can't change monad half way through a do blockClie
What do you mean that you want to keep on using the IO type? Would liftIO $ do ... a block of IO code ... be what you're looking for?Selfimmolation
Following the conclusions from this thread, I've opened a new question of how to integrate print. any help would be greatly appreciated - #30989095Leboeuf
@Leboeuf would you able to say how you used liftIO to convert to UI monad? I have a similar problem: #51907379Pereira
M
8

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.

Merited answered 22/6, 2015 at 13:29 Comment(2)
In my case I'm trying to use some random int list and then display it to screen, it's not clear how to separate the io action to be executed at a later stage thenLeboeuf
UIis an instance of MonadIO, so liftIO is enough to to handle the OP's issue (cf. the answer to the follow-up question). liftIOLater is only meant to be used in a handful of trickier cases (e.g. actions that should be ran during GUI initialisation only after the rest of the GUI has been set up).Alloway
H
5

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.

Hersch answered 22/6, 2015 at 18:15 Comment(0)
H
2

Under the cover, do is simply syntactic sugar for >>= (bind) and let:

do { x<-e; es } =   e >>= \x -> do { es }
do { e; es }    =   e >> do { es }
do { e }        =   e
do {let ds; es} =   let ds in do {es} 

And the type of bind:

(>>=) :: Monad m => a -> (a -> m b) -> m b

So yeah it only "supports" one Monad

Herrick answered 22/6, 2015 at 17:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.