Строгость аргумента dataToTag
В 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
недостаточно для управления порядком оценки?
Ответы
Ответ 1
GHC отслеживает определенную информацию о каждом primop. Одной из ключевых данных является то, является ли primop "can_fail". Первоначальное значение этого флага заключается в том, что примод может выйти из строя, если он может вызвать серьезную ошибку. Например, индексирование массива может вызвать ошибку сегментации, если индекс выходит за пределы диапазона, поэтому операции индексирования могут завершиться неудачей.
Если 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"
Одно из этих дна является исключением; другой - segfault.
dataToTag#
отмечен can_fail. Поэтому GHC видит (в Core) нечто вроде
getTag = \x -> case x of
y -> dataToTag# y
(Обратите внимание, что case
является строгим в Core.) Поскольку dataToTag#
отмечен can_fail, он не будет выплывать из любых выражений case
.