-
{-# RULES "⟨name⟩" forall ⟨binder⟩ ... . ⟨expr⟩ = ⟨expr⟩ ... #-} -
- Где:
-
верхнего уровня
Определяет правило переписывания, используемое для оптимизации исходной программы.
Программист может указать правила переписывания как часть исходной программы (в директиве). Вот пример:
{-# RULES
"map/map" forall f g xs. map f (map g xs) = map (f . g) xs
#-}
Используйте флаг отладки -ddump-simpl-stats, чтобы увидеть, какие правила сработали. Если вам нужна более подробная информация, то -ddump-rule-firings покажет каждое отдельное срабатывание правила, а -ddump-rule-rewrites также покажет, как код выглядит до и после переписывания.
-
-fenable-rewrite-rules -
Разрешает компилятору применять правила переписывания к исходной программе.
6.19.1.1. Синтаксис
С точки зрения синтаксиса:
- В директиве
RULESможет быть ноль или более правил, разделенных точкой с запятой (которая может быть сгенерирована правилом форматирования). -
Правило форматирования применяется в директиве. В настоящее время новый уровень отступа не устанавливается, поэтому если вы поместите несколько правил в одну директиву
RULESи хотите использовать форматирование для их разделения, вам необходимо выровнять начало по той же колонке, что и окружающие определения.{-# RULES "map/map" forall f g xs. map f (map g xs) = map (f . g) xs "map/append" forall f xs ys. map f (xs ++ ys) = map f xs ++ map f ys #-}Кроме того, закрывающая
#-}должна начинаться в колонке правее открывающей{-#. - Каждое правило имеет имя, заключённое в двойные кавычки. Само имя не имеет никакого значения. Оно используется только при сообщении о том, сколько раз правило сработало.
-
Правило может необязательно иметь номер управления фазами (см. Управление фазами), сразу после имени правила. Таким образом:
{-# RULES "map/map" [2] forall f g xs. map f (map g xs) = map (f . g) xs #-}[2]означает, что правило активно в 2-й и последующих фазах. Обратный вариант[~2]также принимается, что означает, что правило активно до, но не включая, 2-ю фазу.Правила поддерживают специальную запись управления фазами
[~], что означает, что правило никогда не активно. Эта функция поддерживает плагины (см. Плагины компилятора), позволяя определять RULE, который никогда не запускается GHC, но при этом парсится, проверяется на типы и т. д., чтобы он был доступен плагину. - Каждая (терм)переменная, упомянутая в правиле, должна быть либо в области видимости (например,
map), либо связана сforall(например,f,g,xs). Переменные, связанные сforall, называются переменными шаблона. Они разделяются пробелами, так же как и в типеforall. -
У переменной шаблона может быть необязательная подпись типа. Если тип переменной шаблона полиморфный, у него обязательно должна быть подпись типа. Например, вот правило
foldr/build:"fold/build" forall k z (g::forall b. (a->b->b) -> b -> b) . foldr k z (build g) = g k zТак как
gимеет полиморфный тип, у него должна быть подпись типа. -
Если включена опция
ExplicitForAll, переменные типа/вида также могут быть явно связаны. Например:{-# RULES "id" forall a. forall (x :: a). id @a x = x #-}Когда присутствует явная переменная типа/вида на уровне типа, каждая переменная типа/вида, упомянутая в нём, должна теперь также быть либо в области видимости, либо связана с
forall. В частности, в отличие от некоторых других мест в Haskell, это означает, что свободные переменные вида не будут неявно связаны. Например:"this_is_bad" forall (c :: k). forall (x :: Proxy c) ... "this_is_ok" forall k (c :: k). forall (x :: Proxy c) ...
Когда нужны связанные переменные типа/вида, оба forall должны всегда включаться, хотя если переменные шаблона не нужны, второй можно оставить пустым. Например:
{-# RULES "map/id" forall a. forall. map (id @a) = id @[a] #-} -
Левая часть правила должна состоять из переменной верхнего уровня, применённой к произвольным выражениям. Например, это не правильно:
"wrong1" forall e1 e2. case True of { True -> e1; False -> e2 } = e1 "wrong2" forall f. f True = True "wrong3" forall x. Just x = NothingВ
"wrong1"левая часть не является применением; в"wrong2"левая часть содержит переменную шаблона в голове. В"wrong3"левая часть состоит из конструктора, а не переменной, применённого к аргументу. - Правило не обязательно должно быть в том же модуле, что и (любая из) переменных, которые оно упоминает, хотя, конечно, они должны быть в области видимости.
- Все правила неявным образом экспортируются из модуля и поэтому действуют в любом модуле, который импортирует модуль, определивший правило, непосредственно или косвенно. (То есть, если A импортирует B, который импортирует C, правила C действуют при компиляции A.) Ситуация очень похожа на ту, что, например, для объявлений переменных.
- Внутри директивы
RULES«forall» обрабатывается как ключевое слово, независимо от других настроек флагов. Кроме того, внутри директивыRULES, расширение языкаScopedTypeVariablesавтоматически включено; см. Лексически связанные переменные типа. - Как и другие директивы, директивы
RULESвсегда проверяются на ошибки области видимости и проверяются на типы. Проверка типов означает, что левая и правая части правила проверяются на типы и должны иметь один и тот же тип. Однако правила активируются только в том случае, если включён флаг-fenable-rewrite-rules(см. Семантика).