I was experimenting with unsafeCoerce
with Int8
and Word8
, and I found some surprising behaviour (for me anyway).
Word8
is a 8 bit unsigned number that ranges from 0-255. Int8
is a signed 8 bit number that ranges from -128..127.
Since they are both 8 bit numbers, I assumed that coercing one to another would be safe, and just return the 8 bit values as if it was signed/unsigned.
For example, unsafeCoerce (-1 :: Int8) :: Word8
I would expect to result in a Word8
value of 255 (since the bit representation of -1 in a signed int is the same as 255 in an unsigned int).
However, when I do perform the coerce, the Word8
the behaviour is strange:
> GHCi, version 7.4.1: http://www.haskell.org/ghc/ :? for help
> import Data.Int
> import Data.Word
> import Unsafe.Coerce
> class ShowType a where typeName :: a -> String
> instance ShowType Int8 where typeName _ = "Int8"
> instance ShowType Word8 where typeName _ = "Word8"
> let x = unsafeCoerce (-1 :: Int8) :: Word8
> show x
"-1"
> typeName x
"Word8"
> show (x + 0)
"255"
> :t x
x :: Word8
> :t (x + 0)
(x + 0) :: Word8
I don't understand how show x
is returning "-1"
here. If you look at map show [minBound..maxBound :: Word8]
, no possible value for Word8
results in "-1"
. Also, how does adding 0 to the number change the behaviour, even if the type isn't changed? Strangely, it also appears it is only the Show
class that is affected - my ShowType
class returns the correct value.
Finally, the code fromIntegral (-1 :: Int8) :: Word8
works as expected, and returns 255, and works correctly with show
. Is/can this code be reduced to a no-op by the compiler?
Note that this question is just out of curiosity about how types are represented in ghc at a low level. I'm not actually using unsafeCoerce in my code.