如何使fmap重写规则触发?

fho*_*fho 13 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
Run Code Online (Sandbox Code Playgroud)

现在我想写一个提取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
Run Code Online (Sandbox Code Playgroud)

直到我偶然添加了一个模块名称:

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
Run Code Online (Sandbox Code Playgroud)

现在,如果我只是在main函数中写出函数应用程序,它仍然无效.

把它们加起来:

  • txt <- fmap head (fmap words (readFile "foo")) 不起作用
  • txt <- fun head words (readFile "foo") 不起作用
  • txt <- fun head words (readFile "foo") 加模块工作
  • fun f g xs = fmap f . fmap g $ xs 加模块不起作用
  • fun f g xs = f <$> (g <$> xs) 加模块工作(但稍后开火)

所有这一切都是通过电话完成的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 ...
Run Code Online (Sandbox Code Playgroud)

Ørj*_*sen 13

鉴于什么@Cactus说,我相信在这里发生的是,该规则Class op fmap 取代fmap与它的方法定义IO:

instance  Functor IO where
    fmap f x = x >>= (pure . f)
Run Code Online (Sandbox Code Playgroud)

如果这种情况发生在任何地方,在您的规则被触发之前,那么fmap您的代码中将不会留下(GHC的内部表示)您自己的规则来触发.

当GHC在特定类型中使用时,GHC会尝试对它们进行特化处理,因此如果单独fmap使用monad 是完全已知的,那么一旦完成专门化就不会有任何泛型fmap.

所以,剩下的问题是,为什么没有你的规则火,当你提供一个模块头?

module Main where
Run Code Online (Sandbox Code Playgroud)

答案在于,如果您不提供任何内容,则与使用的默认模块标头略有不同:

module Main (main) where
Run Code Online (Sandbox Code Playgroud)

请注意,这明确地出口只有 main从模块.您的版本没有导出列表,而是导出模块中定义的所有内容,包括 mainfun.

当只有main出口,GHC可以推断,fun使用在内部main,和内联它完全没有,没有打扰做一个独立版本.然后它注意到fmaps仅用于IO,并专门化它们.或者它可能以相反的顺序执行,但最终结果是相同的.

何时fun也导出,GHC必须假设您的模块的用户可能想要在任何monad中调用它.因此GHC然后编译独立版本的fun一个通用的单子,这保持fmap通用的,您的规则能够触发这个版本.

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