我目前正在学习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
我希望编译的程序打印并退出
几乎立即。
答案 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]
优化为常量,但您甚至不会注意到这种变化。