我有* -> *
类型的Logger类型,可以采用任何类型并将值记录在文件中。我正在尝试以单例方式实现此目的,以便我登录并保持相同的状态。我的代码看起来像
import Control.Applicative
import Control.Monad
import System.IO
import Control.Monad.IO.Class
instance Functor Logger where
fmap = liftM
instance Applicative Logger where
pure = return
(<*>) = ap
newtype Logger a = Logger a deriving (Show)
instance Monad (Logger) where
return = Logger
Logger logStr >>= f = f logStr
instance MonadIO (Logger) where
liftIO a = do
b <- liftIO a
return b
logContent :: (Show a) => a -> Logger a
logContent a = do
b <- liftIO $ logContent2 a
return b
logContent2 :: (Show a) => a -> IO a
logContent2 a = do
fHandle <- openFile "test.log" AppendMode
hPrint fHandle a
hClose fHandle
return (a)
liftIO函数在调用自身时会不断循环。我也无法做到b <-a。有人可以帮助正确实现MonadIO实施吗?
答案 0 :(得分:1)
如评论中所述,我认为您误解了MonadIO
和liftIO
的作用。
这些类型类和函数来自mtl
库。不幸的是,mtl
代表“ monad转换器库”,但mtl is not a monad transformer library代表。相反,mtl
是一组类型类,它们使您可以采用一个---这很重要--- 已经具有特定类型的功能,并为该monad提供一个围绕该功能的一致界面。这最终对于使用实际的monad变压器非常有用。这是因为mtl
允许您使用tell
和ask
和put
来访问Writer
,Reader
和State
功能一致的方式来存储您的monad变压器堆栈。
与此变压器业务分开,如果您已经有一个自定义monad,例如说它支持任意IO并且具有State
功能,则可以定义一个MonadState
实例以进行标准状态操作({ {1}},state
,get
,gets
,put
)可用于您的自定义单子,并且您可以定义一个modify
实例以允许任意使用MonadIO
在您的自定义monad中执行的IO操作。但是,这些类型类都不具备向Monad尚不具备的功能添加功能的能力。特别是,您无法使用liftIO
实例将任意单子动作m a
转换为IO a
。
请注意,MonadIO
程序包包含的类型能够能够向尚不具备的monad添加功能(例如,添加读取器或写入器功能),但是有没有将transformers
添加到任意monad的转换器。这样的变压器是不可能的(没有不安全或不终止的操作)。
还请注意,IO
的签名在liftIO :: MonadIO m => IO a -> m a
上施加了MonadIO
约束,这不仅仅是一个琐碎的约束。它实际上表明m
仅适用于已经具有IO功能的单子liftIO
,因此m
是 IO单子,或者它是一个以m
为基础的monad堆栈。您的IO
示例没有IO功能,因此也没有一个(明智的)Logger
实例。
回到您的特定问题上来,实际上在不完全知道您要做什么的情况下将您引导到这里实际上有点困难。如果您只想将基于文件的日志记录添加到现有的IO计算中,则定义新的转换器堆栈可能会达到目的:
MonadIO
您可以编写如下内容:
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"
monad中使用IO操作时,添加liftIO
调用是很丑陋的,但在很大程度上是不可避免的。
该解决方案还可以将基于文件的日志记录添加到纯计算中,但要理解,如果要安全地记录到文件,则必须将其转换为IO计算。
更通用的解决方案是定义您自己的monad transformer (不仅仅是您自己的monad),例如LogIO
,以及关联的LoggerT m
类型类,该类将添加基于文件的日志记录到任何具有IO功能的monad堆栈。这个想法是,您然后可以创建任意的自定义monad堆栈:
MonadLogger
然后编写代码,混合来自不同层的单子计算(例如混合状态计算和基于文件的日志记录):
type MyMonad = StateT Int (LoggerT IO)
这就是您要执行的操作吗?如果没有,也许您可以在这里或在一个新问题中描述如何尝试将日志添加到示例代码中。