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.
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.
This is more an extended comment - it doesn't address the main question, but your implementation of shufffle
. There are 2 issues with it:
- Your implementation is inefficient - O(n^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.
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