Haskell中使用Writer Monad实现可选日志记录的疑问
问题
假设有如下定义:
fun :: Writer Log a
该函数会生成类型a的值,Log是计算过程中累积的日志类型。为实现可选日志记录,定义了如下类型类及实例:
class Monoid w => Loggable w a where toLog :: a -> w instance Loggable Log Foo where toLog = myToLog instance Loggable () Foo where toLog = mempty
之后将函数改为:
fun :: Loggable w Foo => Writer w a
后续可通过不同实例化w来选择是否记录日志。请问:
- 当
w为()时是否确实无日志记录? - 使用
Log与()的函数归约步骤是否更少? - 有工具可自行验证这些结论吗?
回答
1. 当w为()时确实无有效日志记录
()是Haskell的单元类型,它的Monoid实例定义为:mempty = (),mappend _ _ = ()。结合你定义的Loggable () Foo实例,toLog直接返回mempty也就是()。
在Writer w a的计算流程中,日志会通过mappend累积,但()的mappend始终返回(),整个计算的最终日志只会是不携带任何实际信息的()——等同于没有记录有效日志。
2. 使用()时的归约步骤通常更少
是的。当w为()时,所有和日志相关的操作都是冗余计算:
toLog直接返回(),没有实际的转换逻辑;mappend对()的操作不需要任何计算,直接返回()。
GHC等Haskell编译器会通过死代码消除、常量折叠等优化手段,完全移除这些冗余操作。而使用Log时,需要执行实际的日志生成和合并逻辑,归约步骤自然更多。
3. 可用这些工具自行验证
GHC核心代码查看
编译时添加-ddump-simpl选项,GHC会输出经过优化后的核心代码。对比w为Log和()时的核心代码,能明显看到()版本中已不存在日志相关的冗余计算。
性能分析
使用ghc -prof -fprof-auto编译程序,运行后生成性能报告(.prof文件)。通过报告可以查看两种情况下的函数调用次数、执行时间,()版本的日志相关开销会接近为0。
GHCi单步归约
在GHCi中使用:step命令,对两种版本的fun进行单步归约,手动对比执行的步骤数,能直观看到()版本的步骤更少。
内容的提问来源于stack exchange,提问作者Jason
相关产品推荐
相关产品推荐

