您的类型签名表明您的函数是纯函数(即,它接受一个 Int 并返回一个字符串)但在内部,您正在执行 IO!Haskell 不会让你编写这样的函数。您从文件中读取的任何内容都会永远停留在 IO monad 中,就是这样(当然,不安全的函数除外)。
在这种情况下,结果并不是那么糟糕,因为 Yesod 是一个高度基于 IO 的框架。所有网络流量也都卡在 IO monad 中!
当您在 monad 转换器堆栈中时,您可以在堆栈的每个级别访问 monadic 计算,但只能直接访问其中一个。您用于lift
将计算从 monad 在堆栈中向下移动一层到转换后的 monad。如果IO
在堆栈中,无论向下多少层,您都可以直接通过liftIO
.
所以如果你有type T = ReaderT String IO
那么你可能有一个功能foo :: Int -> T String
。在此函数中,您将在 monad 中进行操作,它将使用monad 功能T
转换monad。在这种情况下,你可以说,而不是得到一个结果,你会得到一个结果!不过,这只是类型中的一个包装,所以不要认为我们做了任何棘手的事情,比如逃离monad。这可能有点令人困惑,所以让我们看一个例子:IO
Reader
lift readFile
IO String
T String
IO String
ReaderT
IO
import Control.Monad.Reader (ReaderT)
import Control.Monad.Writer (WriterT)
import Control.Monad.Trans (lift, liftIO)
type T = ReaderT String IO
getSortedMiddleElement :: Int -> IO String
foo :: Int -> T String
foo n = do
str <- lift $ getSortedMiddleElement n --str holds a pure String now
lift $ putStrLn str --get `putStrLn` from IO and pass the String
return str --let's wrap it back in T now
但是如果我们离 IO 不止一层怎么办?让我们试一试:
type W = WriterT String T -- WriterT String (ReaderT String IO)
-- This doesn't work; lift only gives you access to the next layer's actions
-- but IO is now more than one layer away!
--
--bar n = do
-- str <- lift $ getSortedMiddleElement n
-- Instead, we need liftIO, which will access IO across many transformer layers
bar :: Int -> W String
bar n = do
str <- liftIO $ getSortedMiddleElement n
liftIO $ putStrLn str
return str