В 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
недостаточно для управления порядком оценки?