我有一个小的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 code到Codec.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),以便它可以处理各种大小的整数类型。但这会招致开发人员可能不喜欢的性能损失。尽管如此,这仍然是一个令人困惑的失败,需要深入了解源代码。幸运的是,它很容易解决。