如何使fmap重写规则触发?

时间:2014-10-25 23:19:08

标签: haskell

简单问题:为什么不触发重写规则?

{-# 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 ...

1 个答案:

答案 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。您的版本没有导出列表,而是导出模块中定义的所有内容, mainfun

仅导出main时,GHC可以在fun内部推断出main仅在内部使用,并在那里完全内联,而不必费心一个独立的版本。然后它注意到fmap仅用于IO,并专门用于它们。或者它可能以相反的顺序执行,但最终结果是相同的。

当导出fun时,GHC必须假定模块的用户可能想要在任何monad中调用它。因此,GHC然后为通用monad编译fun的独立版本, 保持fmap通用,并且您的规则可以触发此版本。

但是,即使对于明确的module代码,Class op fmap规则在编译时会触发两次,就像它应用于两个单独的fmap一样。因此,我怀疑即使在这种情况下,fun内联并专注于main 之前您的规则已将其简化为仅使用一个fmap,因此内联main 中使用的版本仍然不会将规则应用于它。