为什么(常量)表达式在Haskell中没有在编译时计算?

时间:2013-10-08 21:51:02

标签: performance haskell optimization compile-time compile-time-constant

我目前正在学习Haskell,有一件事令我感到困惑:

当我构建一个复杂的表达式(其计算需要一些时间)并且该表达式是常量(意味着它只是已知的硬编码值的构建)时,表达式不会在编译时进行求值。

来自C / C ++背景我习惯于这种优化。

在Haskell / GHC中 NOT 执行此类优化(默认情况下)的原因是什么?有什么好处,如果有的话?

data Tree a =
   EmptyTree
 | Node a (Tree a) (Tree a)
 deriving (Show, Read, Eq)

elementToTree :: a -> Tree a
elementToTree x = Node x EmptyTree EmptyTree

treeInsert :: (Ord a) => a -> Tree a -> Tree a
treeInsert x EmptyTree = elementToTree x
treeInsert x (Node a left right)
  | x == a = Node x left right
  | x < a  = Node a (treeInsert x left) right
  | x > a  = Node a left (treeInsert x right)

treeFromList :: (Ord a) => [a] -> Tree a
treeFromList []     = EmptyTree
treeFromList (x:xs) = treeInsert x (treeFromList xs)

treeElem :: (Ord a) => a -> Tree a -> Bool
treeElem x EmptyTree = False
treeElem x (Node a left right)
  | x == a = True
  | x < a  = treeElem x left
  | x > a  = treeElem x right

main = do
  let tree = treeFromList [0..90000]
  putStrLn $ show (treeElem 3 tree)

因为这将总是打印True我希望编译的程序打印并退出 几乎立即。

3 个答案:

答案 0 :(得分:15)

您可能会喜欢this reddit thread。编译器可以尝试这样做,但它可能是危险的,因为任何类型的常量都可以做有趣的事情,比如循环。至少有两种解决方案:一种是超级编译,但尚未作为任何编译器的一部分提供,但您可以尝试各种研究人员的原型;更实际的是使用Template Haskell,这是GHC的机制,让程序员要求在编译时运行一些代码。

答案 1 :(得分:3)

您正在谈论的过程称为超级编译,它比您实现的难度更大。它实际上是计算科学中活跃的研究课题之一!有些人试图为Haskell创建这样的超级编译器(可能基于GHC,我的记忆模糊)但GHC中还没有包含该功能,因为维护人员希望缩短编译时间。你提到C ++是一种可以做到这一点的语言--C ++也恰好有很糟糕的编译时间!

您对Haskell的替代方法是使用Template Haskell手动执行此优化,这是Haskells编译时评估的宏系统。

答案 2 :(得分:2)

在这种情况下,GHC无法确定计算是否完成。这不是懒惰与严格的问题,而是停滞问题。对你来说,看起来treeFromlist [0..90000]是一个可以在编译时计算的常量看起来很简单,但编译器如何知道这一点?编译器可以轻松地将[0..90000]优化为常量,但您甚至不会注意到这种变化。