Ответ 1
Мы можем уменьшить это до
GHCi> toTwosComp (1 :: Word8)
*** Exception: divide by zero
Обратите внимание, что это работает, если вы используете Word16, Int, Integer или любое количество типов, но сбой при использовании Word8, поскольку B.unpack
дает нам! Так почему это терпит неудачу? Ответ находится в исходном коде на 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
Oops! 256 > 255, поэтому мы не можем удерживать его в Word8, и он переполняется беззвучно. toBase
, в процессе его базового преобразования, делит на используемую базу, поэтому он заканчивается делением на ноль, создавая поведение, которое вы получаете.
Какое решение? Преобразуйте Word8s в Ints с помощью fromIntegral
, прежде чем передавать их в toTwosComp
:
convert :: B.ByteString -> [Octet]
convert = map convert' . B.unpack
where convert' b = head $ toTwosComp (fromIntegral b :: Int)
Лично это поведение меня немного беспокоит, и я думаю, что toTwosComp
должно, вероятно, сделать такое преобразование, вероятно, Integer, чтобы оно работало с целыми типами каждого размера; но это повлечет за собой штраф за производительность, который разработчикам может не понравиться. Тем не менее, это довольно запутанный провал, который требует понимания источника. К счастью, это очень легко обойти.