Haskell do clause with multiple monad types

2020-07-06 19:59发布

问题:

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.

回答1:

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.



回答2:

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.



回答3:

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