我有一个小的Haskell程序,很好奇为什么我运行它时会抛出除以零的异常(GHC 7.0.3)

import qualified Data.ByteString.Lazy as B
import Codec.Utils

convert :: B.ByteString -> [Octet]
convert bs = map (head . toTwosComp) $ B.unpack bs

main = putStrLn $ show $ convert $ B.pack [1, 2, 3, 4]

谁能帮我了解这里的事吗?

最佳答案

我们可以减少到

GHCi> toTwosComp (1 :: Word8)
*** Exception: divide by zero

请注意,如果您使用Word16,Int,Integer或任意数量的类型,则此方法有效,但是当使用Word8时失败,因为B.unpack给了我们!那为什么会失败呢?答案可以在source codeCodec.Utils.toTwosComp中找到。您可以看到它调用了toBase 256 (abs x),其中x是参数。
toBase的类型-不是从Codec.Utils模块导出的,并且在源代码中没有显式的类型签名,但是您可以通过将定义放在文件中并询问GHCi类型是什么(:t toBase)来查看此类型
toBase :: (Integral a, Num b) => a -> a -> [b]

因此,在显式注释类型时,toTwosComp调用toBase (256 :: Word8) (abs x :: Word8)。什么是256 :: Word8
GHCi> 256 :: Word8
0

糟糕! 256> 255,因此我们无法将其保存在Word8中,并且它会静默溢出。 toBase在其基数转换过程中,除以使用的基数,因此最终除以零,从而产生您要获得的行为。

有什么解决办法?在将Word8s传递给fromIntegral之前,先使用toTwosComp将Word8s转换为Ints:
convert :: B.ByteString -> [Octet]
convert = map convert' . B.unpack
  where convert' b = head $ toTwosComp (fromIntegral b :: Int)

就个人而言,这种行为让我有些担心,我认为toTwosComp本身可能应该进行这样的转换(可能转换为Integer),以便它可以处理各种大小的整数类型。但这会招致开发人员可能不喜欢的性能损失。尽管如此,这仍然是一个令人困惑的失败,需要深入了解源代码。幸运的是,它很容易解决。

10-08 08:15