Haskell Monads 和 liftIO 我不明白

Haskell Monads and the liftIO I don't get it

大家好,感谢您抽出宝贵时间。

我有一个错误,我不确定错误是什么,但我认为问题是: ext-1.2.4.1:Data.Text.Internal.Lazy.Text IO)Web.Scotty.Internal.Types.ScottyT没有IO转换器。

但我想知道为什么编译器使用 ext-1.2.4.1:Data.Text.Internal.Lazy.Text IO)。这就是为什么我只使用 String 并删除了所有出现的 {-# LANGUAGE OverloadedStrings #-} 但仍然出现错误的原因。另一方面,这应该是 IO [String],不是吗? 正如你所说,我真的不知道 ext-1.2.4.1:Data.Text.Internal.Lazy.Text IO) 是什么。

在另一个地方,我已经成功地使用了 liftIO 作为 a -> IO String 函数。而且我想我以同样的方式使用它们。

我想我慢慢对什么是 monad 有了感觉,但不太确定。 我真的不知道为什么我必须使用 lift 函数。

错误信息:

    • No instance for (MonadIO
                         (Web.Scotty.Internal.Types.ScottyT
                            text-1.2.4.1:Data.Text.Internal.Lazy.Text IO))
        arising from a use of ‘liftIO’
    • In a stmt of a 'do' block:
        paths <- liftIO $ getAllFilePaths2 path
      In the expression:
        do paths <- liftIO $ getAllFilePaths2 path
           pathsToScotty paths
      In an equation for ‘pathsToScotty2’:
          pathsToScotty2 path
            = do paths <- liftIO $ getAllFilePaths2 path
                 pathsToScotty paths
   |        
49 |    paths <- liftIO $ getAllFilePaths2  path

错误发生的地方:

import Control.Monad.IO.Class
...
pathsToScotty2 :: String -> ScottyM ()
pathsToScotty2 path = do
   paths <- liftIO $ getAllFilePaths2  path
   pathsToScotty paths

getAllFilePaths2 :: String -> IO [String]
getAllFilePaths2 dir = do
    putStrLn dir
    isFile <- doesFileExist dir 
    if isFile 
        then return [dir] 
        else do
          dirs <- listDirectory dir
          foldl foldHelper2 (return []) $ map (\d -> show $ mconcat [dir, "/",d ]) dirs


foldHelper2 :: IO [String] -> String  -> IO [String]
foldHelper2 ps path = do 
    paths <- ps
    newPaths <- getAllFilePaths2 path
    return (paths ++ newPaths)

真正理解 monad 需要时间、练习和耐心,但通过检查您的类型不难理解 liftIO 的必要性。

首先,liftIO 的类型是 MonadIO m => IO a -> m a。这意味着该函数可以将任何 IO 操作转换为 monad m 中的操作,只要 m 具有 MonadIO 的实例。理论上,只有 m 有某种处理 IO 动作的方法才能实现,所以这个函数将给定的动作嵌入到 m monad 中。

您绝对是在正确的地方使用 liftIO,为什么它不起作用?也就是说,您有一个 IO [String] 类型的值 getAllFilePaths2 path,并且您希望它是 ScottyM [String] 类型的值——这确实是使用 [=10] 的好地方=].但是,ScottyM 不是 MonadIO 的实例,因为您看到的错误消息试图告诉您,所以您不能使用 liftIO.

这可能看起来很疯狂——你真的可以不将 IO 操作嵌入到 ScottyM 中吗?——但实际上这是有充分理由的。如果 IO 操作抛出错误会怎样?你的整个网络应用程序会崩溃吗?如果您天真地使用 liftIO,它就会出现。相反,scotty 提供了函数 liftAndCatchIO,正如文档所描述的那样,它“类似于 liftIO,但捕获任何 IO 异常并将它们转换为 Scotty 异常。”这是将 IO 动作嵌入 Scotty 的首选方式。

最后的问题来了:请注意 liftAndCatchIO 实际上产生的是 ActionM a 类型的值,而不是 ScottyM a。此外,无法在 ActionM monad 中获取值并将其放入 ScottyM monad。相反,您需要将该值用作操作。所以,我不确定 pathsToScotty 的作用,但很可能您需要重写它。