GHC无声地忽略数值常量中超出范围的位。
这种行为导致我今天遇到了一个相当奇怪的错误:
[0..256]::[Word8] -- evaluates to [0]!
我知道是什么导致了这个错误(在rot256世界中256==0)。。。。我感兴趣的是为什么GHC/Haskell被设计为在编译时不抱怨它。
(这种行为也适用于Int,例如18446744073709551617::Int = 1
)。
我已经习惯了Haskell捕捉琐碎的编译时问题,当我不得不追踪这些问题时,我感到很惊讶。
我怀疑诚实的答案是"因为还没有人实现它"。但我认为这个答案还有另一层,那就是存在一些微妙的设计问题。
例如:我们应该如何知道256
超出了Word8
的范围?好吧,我想一个答案可能是编译器可以注意到Word8
是Integral
、Ord
和Bounded
的所有三个的实例。所以它可以生成类似的检查
(256 :: Integer) > fromIntegral (maxBound :: Word8)
并在编译时评估此检查。问题是,突然之间,我们在编译时运行了潜在的用户编写的代码(例如maxBound
、fromIntegral
和(>)
可能都来自程序员可以编写的实例声明)。这可能有点危险——因为我们不可能知道是否会得到答案!因此,至少您希望默认情况下关闭此检查,并且可能至少与TemplateHaskell一样难以打开
另一方面,也可以只构建我们"信任"的少数实例,例如Word8
和Int
。我会觉得这有点令人失望,尽管也许这样的补丁不会被拒绝。
它取决于单个Num
实例的实现。如果执行
> :type 1
1 :: Num a => a
因此,Haskell最初只是将某些内容转换为泛型Num
,然后指定一个类型Word8
。如果你尝试
> (maxBound :: Word8) + 1
0
> maxBound :: Word8
255
这就是所谓的溢出,在许多语言中都适用,特别是C.Haskell并不阻止您这样做,因为在某些合法的情况下,您可能希望发生溢出。相反,这取决于你,程序员,来确保你的输入数据是有效的。此外,正如jozefg所指出的,在编译时不可能知道每个转换是否有效。
如果您已经有了Eq
、Bounded
和Enum
:,那么您可以实现一个Cyclic
类来提供您想要的行为
class (Eq a, Bounded a, Enum a) => Cyclic a where
next :: a -> a
next a = if a == maxBound then minBound else succ a
prev :: a -> a
prev a = if a == minBound then maxBound else pred a
instance Cyclic Word8
> next 255 :: Word8
0
> prev 0 :: Word8
255
幸运的是,对于所有的Integral
类型,您已经有了Enum
和Eq
,而我所知道的唯一没有Bounded
的Integral
是Integer
。这只是为您想要使用的每个添加instance Cyclic <Int Type>
的问题。