在GHC.Prim
中,我们找到了一个名为dataToTag#的神奇函数:
dataToTag# :: a -> Int#
它根据它使用的数据构造函数将任何类型的值转换为整数。这用于加速Eq
,Ord
和Enum
的派生实现。在GHC源代码中,docs for dataToTag#
解释了该参数应该已经通过评估:
dataToTag#primop应始终应用于已评估的参数。 确保这一点的方法是通过' getTag'来调用它。在GHC.Base包装:
getTag :: a -> Int# getTag !x = dataToTag# x
我觉得我们需要在调用x
之前强制dataToTag#
的评估。我没有得到的是为什么爆炸模式就足够了。 getTag
的定义只是语法糖:
getTag :: a -> Int#
getTag x = x `seq` dataToTag# x
但是,让我们转向docs for seq:
关于评估顺序的注释:表达式
seq a b
不保证在b之前评估a。 seq给出的唯一保证是在seq返回值之前将评估a和b两者。特别是,这意味着可以在a之前评估b。如果您需要保证特定的评估顺序,则必须使用" parallel"中的函数pseq。封装
在Control.Parallel
包的parallel
模块中,文档elaborate further:
... seq在两个参数中都是严格的,因此编译器可以将
a `seq` b
重新排列为b `seq` a `seq` b
...
鉴于getTag
不足以控制评估顺序,seq
如何保证行事有效?
答案 0 :(得分:16)
GHC跟踪有关每个primop的某些信息。一个关键数据是primop" can_fail"。这个标志的最初含义是如果一个primop可能导致硬故障,它就会失败。例如,如果索引超出范围,则数组索引可能会导致分段错误,因此索引操作可能会失败。
如果一个primop失败,GHC会限制它周围的某些转换,特别是不会将它从任何case
表达式中浮出来。这将是相当糟糕的,例如,如果
if n < bound
then unsafeIndex c n
else error "out of range"
编译为
case unsafeIndex v n of
!x -> if n < bound
then x
else error "out of range"
其中一个底部是个例外;另一个是段错误。
dataToTag#
标记为can_fail。所以GHC看到(在Core中)类似
getTag = \x -> case x of
y -> dataToTag# y
(请注意,case
在Core中是严格的。)因为dataToTag#
被标记为can_fail,所以它不会从任何case
表达式中浮出。