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



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

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

从C/C++背景来看,我已经习惯了这种优化。

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

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我希望编译的程序打印并退出几乎立即。

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

你所说的过程称为超级编译,它比你想象的要困难得多。它实际上是计算机科学中活跃的研究课题之一!有些人正在尝试为 Haskell 创建这样的超级编译器(可能基于 GHC,我的记忆很模糊),但该功能尚未包含在 GHC 中,因为维护者希望缩短编译时间。你提到C++是一种这样做的语言——C++也恰好有出了名的糟糕的编译时间!

Haskell的替代方案是使用Template Haskell手动进行此优化,这是Haskell编译时评估的宏系统。

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

最新更新