Haskell 具有多个 monad 类型的 do 子句

Haskell do clause with multiple monad types

我在 Haskell 中使用一个名为 Threepenny-GUI. In this library the main function returns a UI monad 对象的图形库。这让我很头疼,因为当我试图将 IO 值解包到局部变量中时,我收到了抱怨不同 monad 类型的错误。

这是我的问题的一个例子。这是标准 main 函数的略微修改版本,如 Threepenny-GUI 的代码示例所示:

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))

请注意第五行:

labelsAndValues <- shuffle [1..10]

其中returns出现以下错误:

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]

关于我的问题,我如何使用标准箭头符号 (<-) 解压缩 IO 函数,并继续将这些变量作为 IO () 而不是 UI (),所以我可以轻松地将它们传递给其他函数。

目前,我找到的唯一解决方案是使用 liftIO,但这会导致转换为 UI monad 类型,而我实际上想继续使用 IO 类型.

一个do块是针对特定类型的monad,你不能只在中间改变类型。

您可以转换动作,也可以将其嵌套在 do 中。大多数时候,转换会为您准备好。例如,您可以有一个嵌套的 doio 一起使用,然后仅在交互点转换它。

对于您的情况,ThreePennyUI 包提供了一个 liftIOLater 函数来为您处理此问题。

liftIOLater :: IO () -> UI ()

Schedule an IO action to be run later.

为了进行逆向转换,可以使用runUI:

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

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

在幕后,do 只是 >>=(绑定)和 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} 

以及绑定类型:

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

所以是的,只有 "supports" 一个 Monad

这更像是一个扩展评论 - 它没有解决主要问题,而是解决了您对 shufffle 的实施。它有 2 个问题:

  1. 您的实施效率低下 - O(n^2).
  2. IO 不是适合它的类型 - shuffle 没有一般的副作用,它只需要一个随机源。

对于(1)有几种解法:一种是利用Seq及其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得到O(n).

对于 (2),您只需要线程化随机生成器,而不是 IO 的全部功能。已经有不错的库 MonadRandom that defines a monad (and a type-class) for randomized computations. And another package already provides the shuffle function。由于 IOMonadRandom 的一个实例,您可以直接使用 shuffle 作为函数的替代。