В GHC.Prim мы находим магическую функцию с именем dataToTag #:
dataToTag# :: a -> Int#
Он превращает значение любого типа в целое число, основанное на используемом им конструкторе данных. Это используется для ускорения производных реализаций Eq, Ord и Enum. В источнике GHC docs для dataToTag# объясняют, что аргумент должен уже оцениваться:
Приемочный параметр dataToTag # должен всегда применяться к оцениваемому аргументу. Способ обеспечения этого - вызвать его через оболочку getTag в GHC.Base:
getTag :: a -> Int# getTag !x = dataToTag# x
Мне кажется, что нам нужно усилить оценку x до вызова dataToTag#. То, чего я не понимаю, является причиной того, что шаблон взлома является достаточным. Определение getTag - это просто синтаксический сахар для:
getTag :: a -> Int#
getTag x = x `seq` dataToTag# x
Но вернемся к docs для seq:
Примечание по порядку оценки: выражение
seq a bне гарантирует, что a будет оцениваться до b. Единственная гарантия, предоставляемая seq, заключается в том, что как a, так и b будут вычисляться до того, как seq вернет значение. В частности, это означает, что b может быть оценено до a. Если вам нужно гарантировать определенный порядок оценки, вы должны использовать функцию pseq из пакета "parallel".
В модуле Control.Parallel из пакета parallel docs подробно изложит:
... seq строго в обоих своих аргументах, поэтому компилятор может, например, переставить
a `seq` bвb `seq` a `seq` b...
Как получается, что getTag может вести себя как работа, учитывая, что seq недостаточно для управления порядком оценки?