Мы можем уменьшить это до
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, чтобы оно работало с целочисленными типами любого размера; но это повлечет за собой снижение производительности, которое разработчикам может не понравиться. Тем не менее, это довольно запутанный сбой, который требует понимания исходного кода. К счастью, это очень легко обойти.