5

我非常喜欢 Haskell,但我担心 Haskell 的主要问题之一是难以推理空间使用情况。基本上,thunk 和递归的可能性似乎会造成一些棘手的情况,其中似乎必须通过添加正确的严格性来非常小心,以免程序在特定输入上耗尽内存。

我喜欢 C/C++ 的一点是,我可以很快确定程序空间使用的上限,尤其是在避免递归的情况下。变量一目了然。

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

我了解Data.STRef提供可变单元格,但据我了解,这些单元格本身仍然是惰性的,并且可以包含 thunk。我正在考虑强制此类单元格中的数据严格,但我不确定如何以类型系统强制执行的方式执行此操作。

我在想类似“Strict Monad”的东西,但也许这不是这样做的正确形式。

4

3 回答 3

6

我相当肯定在 Haskell 中实现这一点几乎是不可能的,它默认假设是惰性的。例如,标准库中的某些函数在严格的语言中是无用的,因为如果您请求它们的全部输出,它们实际上保证不会终止。因此,如果您有 Haskell 的“严格子集”,则很难与任何其他 Haskell 代码进行互操作,并且无论如何您都会有效地使用另一种语言进行编程。

另外,我认为您通过考虑单子来寻找错误的地方。Data.STRef确实与避免重击和懒惰无关。实际上,您想要的(严格性)与命令式无关,您不需要可变性或单子来获得它。有些编程语言和 Haskell 一样纯粹,但到处都是严格的。例如,我使用过的一个是Mercury

于 2013-04-19T04:42:36.813 回答
1

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

现在我自己的猜测。

在上述讨论中,大多数情况下的基本思想是,您可以!任何类型上加上 a 以获得该类型的严格、明确评估的 WHNF 版本。所以Int可能是一个重击,而!Int绝对不是一个。这为类型检查器提出了有趣的问题。!Int ~ Int持有吗?如果不是——这两者是完全独立的、不兼容的类型——那么使用它们将是非常痛苦的。另一方面,如果它确实成立,则没有什么可以阻止在预期 an 的Int地方传递未评估的!Int- 毕竟,它们是相同的类型。理想情况下,您想要的是能够提供一个!Intwhere 和Int是预期的,但反之则不然。这听起来像一个子类型关系:!a <: a,!a是 的子类型a,a被评估和未评估的值(thunk)占据,而!a只有被评估的值。GHC 的类型系统没有子类型,可能也没有,因为类型系统的其他部分不能很好地与之交互。这是一个高度受限的特定子类型实例:程序员不能声明任意子类型关系,而是存在一个单一的、硬编码的关系:forall a. !a <: a. 我不知道这是否使它更合理地实施。

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

这是我想透的。这似乎比看起来更难。

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

于 2013-04-19T12:32:14.180 回答
1

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

于 2013-04-19T08:37:46.657 回答