简单问题:为什么不触发重写规则?
{-# RULES "fmap/fmap" forall f g xs. fmap f (fmap g xs) = fmap (f.g) xs #-}
main = do
txt <- fmap head (fmap words (readFile "foo.txt"))
print txt
现在我想写一下,提取fun
会触发规则,因为它在之前的测试中完成了......不是这次。
{-# RULES "fmap/fmap" forall f g xs. fmap f (fmap g xs) = fmap (f.g) xs #-}
fun f g xs = fmap f (fmap g xs)
main = do
txt <- fun (drop 1) words (readFile "foo.txt")
print txt
直到我偶然添加了一个模块名称:
module Main where
{-# RULES "fmap/fmap" forall f g xs. fmap f (fmap g xs) = fmap (f.g) xs #-}
fun f g xs = fmap f (fmap g xs)
main = do
txt <- fun head words (readFile "foo.txt")
print txt
现在,如果我在主函数中写出函数应用程序,它仍然无法工作。
总结一下:
txt <- fmap head (fmap words (readFile "foo"))
无法正常工作txt <- fun head words (readFile "foo")
无法正常工作txt <- fun head words (readFile "foo")
plus模块正常工作fun f g xs = fmap f . fmap g $ xs
加上模块无法正常工作fun f g xs = f <$> (g <$> xs)
plus模块确实有效(但稍后会触发)所有这一切都是通过致电ghc --make -O2 -ddump-rule-firings Main.hs
来完成的。样本输出:
# ghc --make -O2 -ddump-rule-firings Main.hs
[1 of 1] Compiling Main ( Main.hs, Main.o )
Rule fired: fmap/fmap
Rule fired: unpack
Rule fired: Class op >>=
Rule fired: Class op fmap
Rule fired: Class op fmap
Rule fired: Class op show
Rule fired: Class op showList
Rule fired: unpack-list
Linking Main ...
答案 0 :(得分:13)
鉴于@Cactus所说的,我认为这里发生的事情是Class op fmap
将fmap
替换为instance Functor IO where
fmap f x = x >>= (pure . f)
:
fmap
如果这种情况发生在任何地方,在您的规则被触发之前,那么您的代码中将没有fmap
(GHC的内部表示),您的规则将被触发。
GHC尝试在特定类型使用时专门化类方法,因此如果在其中使用monad fmap
是完全已知的,则将不会为任何泛型{{1一旦完成专业化就离开了。
所以剩下的问题是,当你提供模块标题时,为什么 你的规则会被激活?
module Main where
答案在于,如果您不提供任何内容,默认模块标题会略有不同:
module Main (main) where
请注意,这会从模块中明确导出 main
。您的版本没有导出列表,而是导出模块中定义的所有内容, main
和fun
。
仅导出main
时,GHC可以在fun
内部推断出main
仅在内部使用,并在那里完全内联,而不必费心一个独立的版本。然后它注意到fmap
仅用于IO
,并专门用于它们。或者它可能以相反的顺序执行,但最终结果是相同的。
当导出fun
时,GHC必须假定模块的用户可能想要在任何monad中调用它。因此,GHC然后为通用monad编译fun
的独立版本, 保持fmap
通用,并且您的规则可以触发此版本。
但是,即使对于明确的module
代码,Class op fmap
规则在编译时会触发两次,就像它应用于两个单独的fmap
一样。因此,我怀疑即使在这种情况下,fun
内联并专注于main
之前您的规则已将其简化为仅使用一个fmap
,因此内联main
中使用的版本仍然不会将规则应用于它。