42

似乎取/返回的常见模式Int(即ByteString.hGetand Data.List.length)与使用强描述类型的 Haskell 模式相反,因为其中许多情况只能处理正数。使用会不会更好Word,或者这些功能是否有偏颇的原因Int

4

3 回答 3

27

确实,Haskell 类型系统的表现力鼓励用户将精确的类型分配给他们定义的实体。但是,经验丰富的 Haskell 人员会欣然承认,必须在最终类型精度(此外,鉴于 Haskell 类型系统的当前限制并不总是可以实现)和便利性之间取得平衡。简而言之,精确类型仅在一定程度上有用。除此之外,它们通常只会造成额外的官僚主义,而收效甚微。

让我们用一个例子来说明这个问题。考虑阶乘函数。对于所有n大于 1 的因素, 的阶乘n是偶数,而 1 的阶乘并不是很有趣,所以让我们忽略那个。因此,为了确保我们在 Haskell 中对阶乘函数的实现是正确的,我们可能会想引入一种新的数字类型,它只能表示无符号偶数:

module (Even) where

newtype Even = Even Integer

instance Num Even where
  ...
  fromInteger x | x `mod` 2 == 0 = Even x
                | otherwise = error "Not an even number."

instance Integral Even where
  ...
  toInteger (Even x) = x

我们将这个数据类型封装在一个不导出构造函数的模块中,使其抽象化,并使其成为所有相关类型类Int的实例。现在我们可以给阶乘以下签名:

factorial :: Int -> Even

sure的类型factorial比我们刚刚说它返回时更精确Int。但是你会发现factorial用这样的类型定义真的很烦人,因为你需要一个乘法版本,将 an(偶数或奇数)Int与 an相乘Even并产生 and EventoInteger更重要的是,您可能不得不在客户端代码中的调用结果中引入无关调用factorial,这可能是造成混乱和噪音的重要来源,而收效甚微。此外,所有这些转换函数都可能对性能产生负面影响。

另一个问题是,当引入一种新的、更精确的类型时,您通常最终不得不复制各种库函数。例如,如果您引入List1 a非空列表的类型,那么您将不得不重新实现许多Data.List已经提供的功能,但[a]仅限于此。当然,然后可以将这些函数方法ListLike设为类类型。但是你很快就会得到各种各样的临时类型类和其他样板文件,而且收益也不大。

最后一点是,不应将Word其视为Int. Haskell 报告未指定实际大小Int,仅保证此类型应能够表示 [− 2 29 , 2 29 − 1] 范围内的整数。Word据说该类型提供未指定宽度的无符号整数。不能保证在任何符合要求的实现中 a 的宽度Word对应于 a 的宽度Int

尽管我提出要防止过度类型扩散,但我确实承认引入一种Natural自然类型可能会很好。不过,最终,Haskell 是否应该有一个专门的自然数类型,除了Int,IntegerWord* 各种类型,很大程度上取决于口味。而目前的事态很可能在很大程度上只是历史的偶然。

于 2012-09-14T22:42:57.967 回答
24

与 C 中的推理相同。使用更精确类型的原因是为了防止错误。在这种情况下,错误,例如尝试在没有意义的地方使用负数。但是在 C 中,Word上溢或下溢的行为unsigned int是回绕。Word如果您尝试在预期使用(or ) 的地方使用负数unsigned int,您不会让编译器对您大喊大叫,甚至不会在运行时出现异常。你得到一个很大的正数。您无法将其与任何其他(“合法”)大正数区分开来!

看这个:

Prelude Data.Word> -1 :: Word
4294967295

不是让错误无法犯下,而是让它们无法被发现。使用Int(and int),您至少可以手动检查负值。和Wordunsigned int你什么都没有。

有价值的是无符号类型,它通过抛出异常来对溢出或下溢做出反应。这仍然不会使错误成为不可能,但它会使它们更容易被发现。但是,它会以性能为代价。*我不知道是否可以在编译时排除它们,但这似乎并不容易。

* 至少,x86 需要额外的指令——在每次操作之后!-- 检查是否发生上溢或下溢。我不知道是否有一种“免费”的架构,虽然它会很好。或者也许是一个可区分的 NaN 值,就像我们对浮点数(可能不是最负数)所拥有的那样,它可以用来表示不可表示的值......

于 2012-09-15T16:09:19.533 回答
7

我的第一个猜测是,无符号算术有一些问题,如果你不注意的话会导致愚蠢的错误:

Prelude Data.Word> let x = 0 :: Word in if x - 1 > x then "Ouch" else "Ohayoo"
"Ouch"

它会在看起来是正确的多态函数中出现一些问题:

Prelude Data.Word> let f acc x y = if x <= y then f (acc + 1) x (y - 1) else acc
Prelude Data.Word> :t f
f :: (Num a1, Num a, Ord a) => a1 -> a -> a -> a1

使用标准长度函数:

Prelude Data.Word> let len' x = if null x then 0 else 1 + len' (tail x) :: Int
Prelude Data.Word> :t len'
len' :: [a] -> Int

Prelude Data.Word> f 0 0 (len' [1,2,3])
4

并使用长度函数返回Word

Prelude Data.Word> let len x = if null x then 0 else 1 + len (tail x) :: Word
Prelude Data.Word> :t len
len :: [a] -> Word

Prelude Data.Word> f 0 0 (len [1,2,3])
...diverges...

而且,当然,如果这些函数返回Word而不是s Int,则需要不断将Words 转换为Ints 以在其他常见地方使用它。

于 2012-09-14T23:00:19.740 回答