我正在尝试使用unsafeCoerce
和Int8
的Word8
,但发现了一些令人惊讶的行为(无论如何对我来说)。Word8
是8位无符号数字,范围为0-255。 Int8
是一个带符号的8位数字,范围为-128..127。
由于它们都是8位数字,因此我认为将两者强制转换是安全的,并且只需返回8位值就好像它是带符号的/无符号的一样。
例如,我希望unsafeCoerce (-1 :: Int8) :: Word8
的结果是Word8
值为255(因为有符号int中的-1的位表示形式与无符号int中的255相同)。
但是,当我执行强制时,Word8
的行为很奇怪:
> 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
我不明白
show x
在这里如何返回"-1"
。如果查看map show [minBound..maxBound :: Word8]
,则Word8
可能没有"-1"
的值。另外,即使类型不变,如何在数字上加0也会改变行为?奇怪的是,它似乎也只是受影响的Show
类-我的ShowType
类返回正确的值。最后,代码
fromIntegral (-1 :: Int8) :: Word8
可以正常工作,并返回255,并且可以与show
一起正常使用。编译器是否可以将此代码简化为无操作?请注意,这个问题只是出于好奇,不足以在ghc中表示类型。我实际上没有在代码中使用unsafeCoerce。
最佳答案
就像@kosmikus所说的一样,Int8
和Int16
都使用Int#
实现,在32位体系结构上,它的宽度为32位(而Word8
和Word16
在后台是Word#
)。 GHC.Prim中的This comment对此进行了更详细的说明。
因此,让我们找出为什么这种实现选择会导致您看到的行为:
> let x = unsafeCoerce (-1 :: Int8) :: Word8
> show x
"-1"
Show
is defined as的Word8
实例instance Show Word8 where
showsPrec p x = showsPrec p (fromIntegral x :: Int)
fromIntegral
只是fromInteger . toInteger
。 toInteger
的Word8
定义为toInteger (W8# x#) = smallInteger (word2Int# x#)
smallInteger
(在integer-gmp中定义)在哪里smallInteger :: Int# -> Integer
smallInteger i = S# i
word2Int#
是类型为Word# -> Int#
的primop-C++中reinterpret_cast<int>
的类似物。这就解释了为什么在第一个示例中看到-1
的原因:该值只是重新解释为有符号整数并打印出来。现在,为什么在
0
中添加x
会为您提供255
呢?查看Num
的Word8
实例,我们看到以下内容:(W8# x#) + (W8# y#) = W8# (narrow8Word# (x# `plusWord#` y#))
因此,看来
narrow8Word#
primop是元凶。让我们检查:> import GHC.Word
> import GHC.Prim
> case x of (W8# w) -> (W8# (narrow8Word# w))
255
的确是。这就解释了为什么加0并不是无操作-
Word8
加法实际上会将值限制在预期范围内。