例如,假设我要在列表上编写一个单子(monad)图和非单子(monad)图。我将从一元论开始:
import Control.Monad
import Control.Monad.Identity
mapM' :: (Monad m) => (a -> m b) -> ([a] -> m [b])
mapM' _ [] = return []
mapM' f (x:xs) = liftM2 (:) (f x) (mapM f xs)
现在,我想重用代码来编写纯
map
(而不是重复代码):map' :: (a -> b) -> ([a] -> [b])
map' f = runIdentity . mapM' (Identity . f)
有什么必要使
map'
像written explicitly like map
is一样被优化? 特别是:{-# SPECIALIZE mapM' :: (a -> Identity b) -> ([a] -> Identity [b]) #-}
还是GHC会优化
map'
本身(通过完全排除Identity
)? map'
的显式编写代码来验证已编译map
的优化程度如何? 最佳答案
好吧,让我们问一下编译器本身。
编译模块
module PMap where
import Control.Monad
import Control.Monad.Identity
mapM' :: (Monad m) => (a -> m b) -> ([a] -> m [b])
mapM' _ [] = return []
mapM' f (x:xs) = liftM2 (:) (f x) (mapM f xs)
map' :: (a -> b) -> ([a] -> [b])
map' f = runIdentity . mapM' (Identity . f)
与ghc -O2 -ddump-simpl -ddump-to-file PMap.hs
(ghc-7.6.1、7.4.2产生相同的结果,唯一名称除外)为map'
产生以下核心PMap.map'
:: forall a_afB b_afC. (a_afB -> b_afC) -> [a_afB] -> [b_afC]
[GblId,
Arity=2,
Caf=NoCafRefs,
Str=DmdType LS,
Unf=Unf{Src=<vanilla>, TopLvl=True, Arity=2, Value=True,
ConLike=True, WorkFree=True, Expandable=True,
Guidance=IF_ARGS [60 30] 160 40}]
PMap.map' =
\ (@ a_c) (@ b_d) (f_afK :: a_c -> b_d) (eta_B1 :: [a_c]) ->
case eta_B1 of _ {
[] -> GHC.Types.[] @ b_d;
: x_afH xs_afI ->
GHC.Types.:
@ b_d
(f_afK x_afH)
(letrec {
go_ahZ [Occ=LoopBreaker]
:: [a_c] -> Data.Functor.Identity.Identity [b_d]
[LclId, Arity=1, Str=DmdType S]
go_ahZ =
\ (ds_ai0 :: [a_c]) ->
case ds_ai0 of _ {
[] ->
(GHC.Types.[] @ b_d)
`cast` (Sym <(Data.Functor.Identity.NTCo:Identity <[b_d]>)>
:: [b_d] ~# Data.Functor.Identity.Identity [b_d]);
: y_ai5 ys_ai6 ->
(GHC.Types.:
@ b_d
(f_afK y_ai5)
((go_ahZ ys_ai6)
`cast` (<Data.Functor.Identity.NTCo:Identity <[b_d]>>
:: Data.Functor.Identity.Identity [b_d] ~# [b_d])))
`cast` (Sym <(Data.Functor.Identity.NTCo:Identity <[b_d]>)>
:: [b_d] ~# Data.Functor.Identity.Identity [b_d])
}; } in
(go_ahZ xs_afI)
`cast` (<Data.Functor.Identity.NTCo:Identity <[b_d]>>
:: Data.Functor.Identity.Identity [b_d] ~# [b_d]))
}
是的,只有cast
,没有实际开销。您会得到一个本地工作的go
,其作用与map
完全相同。总结:您只需要
-O2
,并且可以通过查看内核(-ddump-simpl
)或在生成的程序集(-ddump-asm
)或LLVM位代码-ddump-llvm
(如果可以阅读)中验证代码的优化程度。详细说明可能是一件好事。关于
有必要写吗
{-# SPECIALIZE mapM' :: (a -> Identity b) -> ([a] -> Identity [b]) #-}
还是GHC会优化map'
本身(通过完全排除身份)?答案是,如果您在与定义常规函数相同的模块中使用专业化,那么通常您不需要
{-# SPECIALISE #-}
编译指示,GHC会在看到任何好处的情况下自行创建专业化。在以上模块中,GHC创建了专业化规则"SPEC PMap.mapM' [Data.Functor.Identity.Identity]" [ALWAYS]
forall (@ a_abG)
(@ b_abH)
($dMonad_sdL :: GHC.Base.Monad Data.Functor.Identity.Identity).
PMap.mapM' @ Data.Functor.Identity.Identity
@ a_abG
@ b_abH
$dMonad_sdL
= PMap.mapM'_$smapM' @ a_abG @ b_abH
这也有利于在定义模块之外的mapM'
monad上使用Identity
(如果进行了优化编译,并且该monad及时被识别为Identity
以便触发规则)。但是,如果GHC不了解可以专精的类型,那么它可能看不到任何好处,也无法专精(我不知道它是否会尝试使用-到目前为止,我已经找到了每种专精时间我看)。
如果您想确定,请查看核心。
如果您需要在其他模块中进行专业化处理,则GHC在编译定义模块时没有理由对功能进行专业化处理,因此在这种情况下,必须进行编译。从ghc-7开始,最好使用
{-# SPECIALISE #-}
编译指示,而不是{-# INLINABLE #-}
编译指示要求对一些手动选择的类型进行专业化处理,以便更好地使用{#- INLINABLE #-}
编译指示,以便在导入模块中可以访问(略作修改的)源代码,允许在那里进行任何所需类型的专业化。还有什么需要补充的吗?
当然,不同的用法可能需要不同的编译指示,但根据经验,
{-# RULES #-}
是您最想要的。当然map'
可以实现编译器无法自行完成的功能。如何验证为
map
明确编写的代码,对编译后的ojit_code的优化程度如何?