似乎取/返回的常见模式Int
(即ByteString.hGet
and Data.List.length
)与使用强描述类型的 Haskell 模式相反,因为其中许多情况只能处理正数。使用会不会更好Word
,或者这些功能是否有偏颇的原因Int
?
3 回答
确实,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 Even
。toInteger
更重要的是,您可能不得不在客户端代码中的调用结果中引入无关调用factorial
,这可能是造成混乱和噪音的重要来源,而收效甚微。此外,所有这些转换函数都可能对性能产生负面影响。
另一个问题是,当引入一种新的、更精确的类型时,您通常最终不得不复制各种库函数。例如,如果您引入List1 a
非空列表的类型,那么您将不得不重新实现许多Data.List
已经提供的功能,但[a]
仅限于此。当然,然后可以将这些函数方法ListLike
设为类类型。但是你很快就会得到各种各样的临时类型类和其他样板文件,而且收益也不大。
最后一点是,不应将Word
其视为Int
. Haskell 报告未指定实际大小Int
,仅保证此类型应能够表示 [− 2 29 , 2 29 − 1] 范围内的整数。Word
据说该类型提供未指定宽度的无符号整数。不能保证在任何符合要求的实现中 a 的宽度Word
对应于 a 的宽度Int
。
尽管我提出要防止过度类型扩散,但我确实承认引入一种Natural
自然类型可能会很好。不过,最终,Haskell 是否应该有一个专门的自然数类型,除了Int
,Integer
和Word*
各种类型,很大程度上取决于口味。而目前的事态很可能在很大程度上只是历史的偶然。
与 C 中的推理相同。使用更精确类型的原因是为了防止错误。在这种情况下,错误,例如尝试在没有意义的地方使用负数。但是在 C 中,Word
上溢或下溢的行为unsigned int
是回绕。Word
如果您尝试在预期使用(or ) 的地方使用负数unsigned int
,您不会让编译器对您大喊大叫,甚至不会在运行时出现异常。你得到一个很大的正数。您无法将其与任何其他(“合法”)大正数区分开来!
看这个:
Prelude Data.Word> -1 :: Word
4294967295
不是让错误无法犯下,而是让它们无法被发现。使用Int
(and int
),您至少可以手动检查负值。和Word
,unsigned int
你什么都没有。
有价值的是无符号类型,它通过抛出异常来对溢出或下溢做出反应。这仍然不会使错误成为不可能,但它会使它们更容易被发现。但是,它会以性能为代价。*我不知道是否可以在编译时排除它们,但这似乎并不容易。
* 至少,x86 需要额外的指令——在每次操作之后!-- 检查是否发生上溢或下溢。我不知道是否有一种“免费”的架构,虽然它会很好。或者也许是一个可区分的 NaN 值,就像我们对浮点数(可能不是最负数)所拥有的那样,它可以用来表示不可表示的值......
我的第一个猜测是,无符号算术有一些问题,如果你不注意的话会导致愚蠢的错误:
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
,则需要不断将Word
s 转换为Int
s 以在其他常见地方使用它。