键入强制执行的“严格/不完整”的Haskell子集/版本

时间:2013-04-19 03:59:40

标签: haskell lazy-evaluation strictness

我非常喜欢Haskell,但是关于Haskell的主要问题之一是关于空间使用的推理存在困难。基本上,thunks和recursion的可能性似乎会产生一些棘手的情况,似乎必须非常小心地添加正确的严格性,以免程序在特定输入上耗尽内存。

我喜欢C / C ++的是我可以很快确定程序空间使用的上限,特别是如果避免递归的话。变量很清楚。

我想知道的是,是否有办法创建Haskell的类型安全“命令式”子集,它没有thunk,并且是严格的。

我理解Data.STRef给出了可变细胞,但据我所知,这些细胞本身仍然是懒惰的并且可以包含thunk。我在想强制这些单元格中的数据是严格的,但我不确定如何以类型系统强制执行的方式执行此操作。

我在想像“严格的Monad”这样的东西,但也许这不是正确的形式。

3 个答案:

答案 0 :(得分:6)

我相当肯定在Haskell中实现这一点几乎是不可能的,默认情况下它实际上是假设懒惰。例如,标准库中的所有函数在严格的语言中都是无用的,因为如果您请求它们的整个输出,它们实际上保证不会终止。所以如果你有一个"严格的子集"对于Haskell来说,很难与任何其他Haskell代码进行互操作,而且无论如何你都可以用其他语言进行编程。

另外,我认为你通过考虑monad来寻找错误的地方。 Data.STRef确实与避免暴行和懒惰无关。事实上你想要的(严格)与命令无关,你不需要可变性或monad来获得它。有些编程语言和Haskell一样纯粹,但是到处都是严格的。我曾与之合作的是Mercury,例如。

答案 1 :(得分:1)

您可能有兴趣阅读BangPatterns language extensionUnboxed types/operations。但是你也应该知道任何函数总是具有盒装类型的含义。优化的责任是通过根据“bangs”和其他函数编译代码来消除任何ref-kind值。

答案 2 :(得分:1)

我想到了这一堆。其他人也是如此:

现在我自己的推测。

在上面的讨论中,大多数时候基本的想法是你可以在任何类型上加上!来获得该类型的严格的,肯定评估的WHNF版本。所以Int可能是一个thunk,而!Int绝对不是一个。这为typechecker提出了有趣的问题。 !Int ~ Int有效吗?如果没有 - 两者是完全分离的,不兼容的类型 - 那么使用它们将是非常痛苦的。另一方面,如果 成立,那么就没有什么可以阻止传递未评估的Int,其中!Int是预期的 - 毕竟,它们是相同的类型。您理想的是能够提供!Int,其中Int是预期的,但反之亦然。这听起来像是子类型关系:!a <: a!aa的子类型,a由估值值和未评估值(thunks)居住,而{{1}只有被评估的。 GHC的类型系统没有子类型,也可能没有,因为类型系统的其他部分与它没有很好的交互。这是一个高度限制的,特定的子类型实例:程序员不会声明任意子类型关系,而是存在单个硬编码关系:!a。我不知道这是否使其更合理地实施。

假设可以完成。你还有其他问题。如果您尝试提供forall a. !a <: a预计会有Int,该怎么办?输入错误?理想情况下,我认为,它不会,而是编译器会插入代码来评估它,并继续。好的。如何提供!Int预期[Int],或[!Int]f a一般情况?编译器怎么可能知道如何遍历任何给定的结构来找到它包含f !a的那些点,来评估那些,只有那些?那么 会出现类型错误吗?让我们说它是。程序员如何手动完成转换 - 从a获取f !a?也许通过映射函数f a?但那是荒谬的。 Haskell普遍懒惰。如果将它应用于参数eval :: a -> !a,那么在需要它的值之前,它将是一个thunk。因此eval x不可能有eval x类型。返回类型位置中的严格注释没有任何意义。那么:!adata Wrap a = Wrap { unwrap :: a },语义eval' :: a -> Wrap !a可能是一个thunk,但是编译器会插入代码,这样当你评估它时,Wrap !a里面会{肯定会被评估?实际上,这是一个更简单的表述:!adata Wrap' a = Wrap' { unwrap' :: !a })。这是现有的合法Haskell,由我们新的严格类型包含。我觉得这很好。但是一旦你尝试使用它,你就会再次遇到问题。您如何再次从eval' = Wrap'转到f a - f !a?但是fmap unwrap . fmap Wrapunwrap存在完全相同的问题。所以这一切似乎都不是那么微不足道。那看似无害的反向案例呢:提供eval预期f !a的位置?那样有用吗? (换句话说,f a?)这取决于f !a <: f aa的使用方式。编译器必须具有协变,逆变和不变类型参数位置的知识 - 这是子类型带来的另一件事。

就我所想的而言。它似乎比看起来更难。

还有一件有趣的事情。您可能已经或可能没有听说过未提升类型的概念:底部没有居住的类型。据我所知,就是这样。保证对WHNF进行评估的类型;保证不是底部的类型。没有区别,对吗? GHC实际上已经有一堆未提升的类型作为原语(f等),但是它们被连接(你不能添加新的)并且除了被解除之外还没有装箱,所以他们有一个不同种类(Int#而不是#),不能与普通类型混合使用。而*将是一种未提升但装箱的类型!a。未提升的类型是我在类型理论上下文中已经提到过几次的东西,所以也许已经进行了一些研究,以更一般的方式实现它们。我还没看过。