考虑以下数据类型,它只是为了说明:
data D where
D1 :: Int -> D
D2 :: String -> D
DJ :: D -> D -> D
也许还有一个函数,比如 toString
:
{-# INLINE toString #-}
toString x = case x of
(D1 x) -> "Int: show x"
(D2 x) -> "String: show x"
(DJ x y) -> "(" ++ toString x ++ "," ++ toString y ++ ")"
(值得注意的是我所做的与打印无关,这只是一个说明性示例)
所以我发现通过像这样定义 toString
可以让我的程序快 15 倍:
{-# INLINE toString #-}
toString x = case x of
(D1 x) -> "Int: show x"
(D2 x) -> "String: show x"
(DJ x y) -> undefined
发生的事情是 toString
现在能够被 GHC 内联。这允许在未来进行大量优化。 DJ
案例是导致问题的原因。所以我尝试了这个:
{-# INLINE toString #-}
toString x = case x of
(D1 x) -> intShow x
(D2 x) -> strShow x
_ -> go x
where
go (D1 x) -> intShow x
go (D2 x) -> strShow x
go (DJ x y) -> "(" ++ go x ++ "," ++ go y ++ ")"
intShow x = "Int: show x"
strShow x = "String: show x"
这实际上意味着它编译速度很快。原因是(我很确定)因为 toString
不再是递归的。 go
是,但 toString
不是。所以编译器会很高兴地内联 toString
,允许更多的优化。
但在我看来,上面的代码很丑。
就像我说的,我拥有的功能比这更复杂,而且这种问题在我的代码中出现。我有一个包含很多构造函数的数据类型,有些简单,有些递归。然而,每当我定义一个递归案例时,它甚至会减慢简单的案例。有没有办法保持顶级函数内联而不像我上面那样修改代码?
最佳答案
我没有优雅的解决方案,但也许这样的方法可行。未经测试。
{-# INLINE toString #-}
toString x = go (fix go) -- equivalent to (fix go), but unrolled once
where
{-# INLINE go #-}
go _ (D1 x) -> intShow x
go _ (D2 x) -> strShow x
go k (DJ x y) -> "(" ++ k x ++ "," ++ k y ++ ")"
intShow x = "Int: show x"
strShow x = "String: show x"
关于haskell - 当存在一些递归情况时保持内联的潜力,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42222998/