我有一个小的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]
有谁能帮我理解这里发生了什么?
答案 0 :(得分:17)
我们可以将其减少到
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
的Ints,然后再将其传递给toTwosComp
:
convert :: B.ByteString -> [Octet]
convert = map convert' . B.unpack
where convert' b = head $ toTwosComp (fromIntegral b :: Int)
就个人而言,这种行为让我有点担心,我认为toTwosComp
本身应该进行这样的转换,可能是Integer,因此它适用于各种大小的整数类型;但这会导致性能损失,开发人员可能不喜欢这个想法。尽管如此,这是一个令人困惑的失败,需要潜水源才能理解。值得庆幸的是,它很容易解决。
答案 1 :(得分:5)
map (head . toTwosComp) [1, 2, 3, 4]
正常工作
map (head . toTwosComp) $ B.unpack $ B.pack [1, 2, 3, 4]
导致您描述的异常。让我们看看有什么区别。
> :t [1, 2, 3, 4]
[1, 2, 3, 4] :: Num t => [t]
> :t unpack $ pack $ [1, 2, 3, 4]
unpack $ pack $ [1,2,3,4] :: [Word8]
Word8可能导致问题。我们来看看
> toTwosComp (1 :: Word8)
*** Exception: divide by zero
所以显然我们必须将Word8转换为其他整数类型。
> map (head . toTwosComp . fromIntegral) $ B.unpack $ B.pack [1, 2, 3, 4]
[1,2,3,4]
有效!