让我们看一下 Haskell 的一元解决方案。日志记录背后的想法是,我们的计算有一个额外的方法,可以在某处“输出”写入一条消息。有很多方法可以表示这样的计算,但最通用的方法之一是创建一个 monad:
class (Monad m) => MonadWriter w m | m -> w where
tell :: w -> m ()
类型w
代表消息,功能tell
是将消息“发送”到单子(效果完整)计算中。
笔记:
- Haskell
MonadWriter
实际上更丰富,它包含允许检查和修改的函数w
,但我们暂时先把它放在一边。
- 该
| m -> w
部分对于解释并不重要,它只是意味着w
对于给定的m
.
最常用的实现是Writer
,它基本上只是一对。其中一个元素是计算的结果,另一个元素是一系列书面消息。(实际上它并不是一个真正的序列,它更通用 - 一个幺半群,它定义了将多个消息组合成一个的操作。)您可以通过查看Writer 模块来检查 Haskell 的解决方案。然而,它更普遍地使用WriterT
monad 转换器编写,所以如果你不是 monad 粉丝,它可能很难阅读。同样的事情也可以在其他函数式语言中完成,例如在 Scala 中查看这个示例。
但是上述类型类还有其他可能的、更多面向副作用的(仍然有效的)实现。我们可以定义tell
将消息发送到某个外部接收器,例如标准输出、文件等。例如:
{-# LANGUAGE FunctionalDependencies, TypeSynonymInstances, FlexibleInstances #-}
instance MonadWriter String IO where
tell = putStrLn
在这里,我们说它IO
可以用作将String
s 写入标准输出的日志记录工具。(这只是一个简化的例子,一个完整的实现可能会有一个 monad 转换器,可以tell
为任何IO
基于 - 的 monad 添加功能。)