如评论中所述,我认为您误解了做什么MonadIO
和liftIO
做什么。
这些类型类和函数来自mtl
库。不幸的是,它mtl
代表“monad 转换器库”,但mtl 不是 monad 转换器库。相反,mtl
是一组类型类,允许您采用一个 monad --- 这很重要 ---已经具有特定类型的功能,并为该 monad 提供围绕该功能的一致接口。这最终对于使用实际的 monad 转换器非常有用。这是因为mtl
允许您以一致的方式使用tell
和访问 monad 转换器堆栈的、和ask
功能。put
Writer
Reader
State
与这个转换器业务分开,如果你已经有一个自定义的 monad,比如说支持任意 IO 并具有State
功能,那么你可以定义一个MonadState
实例来使标准状态操作(state
, get
, gets
, put
, modify
)可用于你的自定义 monad,你可以定义一个MonadIO
实例以允许在您的自定义 monad 中执行任意 IO 操作,使用liftIO
. 但是,这些类型类中没有一个能够向 monad 添加它还没有的功能。特别是,您不能将任意一元操作m a
转换为IO a
使用MonadIO
实例。
请注意,该transformers
包包含的类型能够向 monad 添加它还没有的功能(例如,添加读取器或写入器功能),但是没有转换器可以添加IO
到任意 monad。这样的变压器是不可能的(没有不安全或非终止操作)。
另请注意, for 的签名对liftIO :: MonadIO m => IO a -> m a
施加了MonadIO
约束m
,这不仅仅是一个微不足道的约束。它实际上表明liftIO
它只适用于已经具有 IO 功能m
的 monad ,所以要么是IO monad,要么是一个以它为基础的 monad 堆栈。您的示例没有 IO 功能,因此没有(明智的)实例。m
IO
Logger
MonadIO
回到您的具体问题,实际上在不知道您要做什么的情况下将您引导到这里实际上有点困难。如果您只想将基于文件的日志记录添加到现有的 IO 计算中,那么定义一个新的转换器堆栈可能会解决问题:
type LogIO = ReaderT Handle IO
logger :: (Show a) => a -> LogIO ()
logger a = do
h <- ask
liftIO $ hPrint h a
runLogIO :: LogIO a -> FilePath -> IO a
runLogIO act fp = withFile fp AppendMode $ \h -> runReaderT act h
你可以写这样的东西:
main :: IO ()
main = runLogIO start "test.log"
start :: LogIO ()
start = do
logger "Starting program"
liftIO . putStrLn $ "Please enter your name:"
n <- liftIO $ getLine
logger n
liftIO . putStrLn $ "Hello, " ++ n
logger "Ending program"
liftIO
在 monad 中使用 IO 操作时添加调用的需要LogIO
很丑陋,但在很大程度上是不可避免的。
此解决方案也适用于将基于文件的日志记录添加到纯计算中,但如果您想安全地登录到文件,则无论如何都必须将它们转换为 IO 计算。
更通用的解决方案是定义您自己的 monad转换器(不仅仅是您自己的 monad),例如LoggerT m
,以及一个关联的MonadLogger
类型类,它将基于文件的日志记录添加到任何支持 IO 的 monad 堆栈。这个想法是您可以创建任意自定义 monad 堆栈:
type MyMonad = StateT Int (LoggerT IO)
然后编写混合来自不同层的单子计算的代码(如混合状态计算和基于文件的日志记录):
newSym :: String -> MyMonad String
newSym pfx = do
n <- get
logger (pfx, n)
put (n+1)
return $ pfx ++ show n
这就是你想要做的吗?如果没有,也许您可以在此处或在新问题中描述您如何尝试将日志记录添加到某些示例代码中。