Rewriter implementation notes
See also
Data rewriters — user-facing API for the rewriter
(data::rewriter class, the Rewriter concept, and how to use rewriters
from algorithms).
This section provides the mathematical foundations for the Rewriter concept and its operations, based on [weerdenburg2009] and [vanderwulp2009].
Introduction
Higher order rewriting
There are several formalisms for higher order rewriting. We choose higher-order rewriting systems (HRSs) introduced by Nipkow. In [raamsdonk2001] HRSs are summarized as follows.
In a HRS we work modulo the -relation of simply typed
-calculus. Types are built from a non-empty set of base types
and the binary type constructor
as usual. For every type we
assume a countably infinite set of variables of that type, written as
. A signature is a non-empty set of typed function
symbols. The set of preterms of type
over a signature
consists exactly of the expressions
for which we can derive
using the following rules:
for a variable
of type
,
for a function symbol
of type
in
,
if
, and
and
, then
,
if
and
, then
.
The abstraction operator binds variables, so occurrences of
in
in the preterm
are bound. We work modulo
type-preserving
-conversion and assume that bound variables are
renamed whenever necessary in order to avoid unintended capturing of free
variables. Parentheses may be omitted according to the usual conventions. We
make use of the usual notions of substitution of a preterm
for the
free occurrences of a variable
in a preterm
, notation
, and replacement in a context, notation
. We
write
if
is a subpreterm of
, and
use
for the strict subpreterm relation.
The -reduction relation, notation
, is
the smallest relation on preterms that is compatible with formation of preterms
and that satisfies the following:
The restricted -expansion relation, notation
, is defined as follows. We have
if , and
is a fresh variable, and no
-redex is created (hence the terminology restricted
-expansion). The latter condition is satisfied if
is not
an abstraction (so not of the form
), and doesn’t occur in
as the left part of an application (so doesn’t occur in a
sub-preterm of the form
).
In the sequel we employ only preterms in -normal form, where
every sub-preterm has the right number of arguments. Instead of
we often write
. A
preterm is then of the form
with
of base type and all
in
-normal form.
A term is a preterm in -normal form. It is also in
-normal form because
-normal forms are closed
under
-reduction. A term is of the form
with
a function symbol or
a variable. Because the
-reduction relation is confluent
and terminating on the set of preterms, every
-equivalence
class of preterms contains a unique term, which is taken as the representative
of that class.
Because in the discussion we will often use preterms, we use here the notation
for the replacement of variables according to the substitution
(without reduction to
-normal form), and write
explicitly
for its
-normal form.
This is in contrast with the usual notations for HRSs.
A rewrite rule is a pair of terms , written as
, satisfying the following requirements:
and
are of the same base type,
is of the form ,
all free variables in
occur also in
,
a free variable
in
occurs in the form
with
-equivalent to different bound variables.
The last requirement guarantees that the rewrite relation is decidable because
unification of patterns is decidable. The rewrite rules induce a rewrite
relation on the set of terms which is defined by the
following rules:
if
then
,
if
then
,
if
then
,
if
is a rewrite rule and
is a substitution then
.
The last clause shows that HRSs use higher-order pattern matching, unlike AFSs, where matching is syntactic.
mCRL2 terms
In mCRL2 we have the following terms:
where is a term,
is a variable and
is a function
symbol.
Remark
This needs to be further elaborated. Terms are typed, and function symbols
(and terms?) have an arity. The term is rather
unusual, but it is covered by HRSs (?).
Remark
In fact the mCRL2 language uses slightly more general terms:
,
,
and
.
For a rewrite algorithm the following rules are suggested:
where is a fresh variable not appearing in
.
Remark
In a rewrite algorithm, the term types are unused. One can add correctness checks for proper typing however.
Remark
Types in mCRL2 need to be rewritten to normal form as well. A very simple rewrite system can be defined for this.
Remark
What about normal forms for terms containing -expressions
and/or quantifiers? Expressions can be equal modulo alpha-conversion, so
ATerm equality doesn’t work here.
Remark
In a rewrite algorithm one has to explicitly describe where
-conversion and
-reduction is being done. Doing
-expansion is probably not necessary.
Types
A base type is a non-function type; typical examples are the Booleans or
Natural numbers. Let be a non-empty set of base types and
. The set of types is inductively defined as follows:
where is the function-type constructor. The type constructor
associates to the left, for example:
Product types are often not present in treatment of simply-typed lambda calculus. We need them to type non-lambda terms.
The arity of a type is a natural number, denoted
, which is inductively defined on the structure of
as follows:
A signature is a non-empty set of function symbols each of
which has a type. We write
to denote that symbol
has
type
and extend the notion of arity to symbols such that if
then
. Symbols with
arity zero are called constants.
Let be a signature and let
be a countably finite
set of variables of type
such that
,
for each type
. The set of terms over
, denoted
, is inductively defined as:
Let
be a variable of type
; then
.
Let
be a function symbol of type
, and
for all
; then
is a term of type
.
Let
, and
for all
; then
is a term of type
.
Let
be a variable of type
and
a term of type
; then
is a term of type
.
Simple terms
Simple terms are terms with the following syntax:
where is a term,
is a variable and
is a function
symbol.
Applicative terms
Applicative terms are an extension of simple terms:
The set of all variables is denoted by , the set of all
function symbols by
and the set of all terms by
. In this document we use the convention that
, that
, and that
.
We write for the set of variables that occur in
. Formally:
Subterms
To facilitate operations on subterms we inductively define positions
() as follows. A position is either
(the
empty position) or an index
(from
) combined with
a position
, notation
. We lift
to
an associative operator on positions with
as its unit element
and often write just
for the position
. We
write the subterm of
at position
as
and
we write term
with the subterm at position
replaced by
as
. These operations are defined as follows:
Some examples are:
Substitutions
A substitution is a function .
A substitution
can also be applied to a term
. This is
denoted by
and is defined as:
For terms containing λ-abstractions, quantifiers and where clauses, a more refined notion of capture-avoiding substitution is needed; see the Capture-avoiding substitutions page.
Rewrite rules
A rewrite rule is a rule , with
. We put three restrictions on rewrite rules:
For a set of rewrite rules we define the rewrite relation
as follows:
if there is a rule
in
, a position
and a substitution
such that
where is a boolean function that determines if a condition is true.
We write
instead of
if no confusion
can occur. We write
for the reflexive and transitive
closure of
and
if there is no
such that
. A normal form is a term
such that
and
.
Rewrite algorithm
We now formulate an abstract rewrite algorithm , where
we assume that
is a given, fixed set of rewrite rules:
Note that this algorithm does not need to terminate. In practice we are also
interested in an algorithm , that applies a
substitution
to the variables in
during rewriting. The
specification of this algorithm is simply
The reason we are interested in such an algorithm is that it can be implemented
more efficiently than the straightforward solution to first compute
and then compute
.
Match trees
A match tree is a tree structure that represents a number of rewrite rules that have left-hand sides with the same function symbol as head. It is used to compute all possible results of applying one of these rules to a term. Match trees are only defined for simple terms. A match tree consists of nodes of the following types:
: If the current term has the form
replace the top of the stack by
and continue with
, otherwise continue with
.
: Assign the current term to variable
and continue with
.
: If the current term is equal to
continue with
, otherwise continue with
.
: Return
.
: Return the empty set.
: Remove
elements from the stack and continue with
. We abbreviate
as
.
: If the stack is not empty continue with
, otherwise continue with
.
: If
evaluates to
, continue with
, otherwise continue with
.
where is a function symbol,
is a variable,
is a
term,
is a set of terms annotated with a rewrite rule, and
and
are match tree nodes.
Evaluating a match tree
Let be a sequence of terms, and let
be an arbitrary
substitution function. Then the evaluation of a match tree with arguments
and
is a set of terms and is defined as follows:
where and
are match trees,
and
are
function symbols,
and
are sequences of terms and
and
are terms. The evaluation of a match tree
in a single
term
with substitution
is defined as
.
Building a match tree
Let be a rewrite rule given by
. Then we
define
,
where
is defined as:
Match trees are only defined for rewrite rules with simple terms at the left-hand side.
Joining match trees
Two match trees and
can be joined
into one using the operator
, defined as follows.
where is defined as
,
, etc.
Optimizing match trees
The result of joining match trees is often not optimal. This section gives two
algorithms and
to optimize match
trees.
with
The algorithm if
, where
is defined as:
Prioritized rewrite rules
By adding priorities to rewrite rules, the selection of rewrite rules considered
for a term can be reduced. We model priorities of rewrite rules using a function
, that returns the rules of highest priority for a set of rules.
So
and
if and only
if
. We define a function
that
applies a priority function
to a match tree:
The effect of applying to a match tree is that the
-nodes will contain fewer elements. This can be useful to remove
unwanted results. Consider for example the rewrite system
This system can have both and
as a
result of rewriting the term
. But if we
give the first equation a higher priority than the second, the undesired
derivation
is eliminated.
Rewriting
In this section we describe rewriting strategies. For the moment we only consider innermost rewriting.
Rewriting using match trees
Suppose that we have a rewrite system, and that for each function symbol
a match tree
has been constructed that corresponds to
rewrite rules with head symbol
. We define the function
as:
Innermost rewriting
We now define an algorithm for innermost rewriting. It
is defined for applicative terms. We assume that
is always in
normal form already.
Further work
Extend the definition of terms with lambda expressions and quantifier expressions, and extend the algorithms so they can handle them.
Design an algorithm for rewriting using strategies as defined in [weerdenburg2009].
Extend the rewrite algorithms so they handle evaluation of conditions (as required in the evaluation of a
-node).
Extend the rewrite algorithms for rewrite rules with more general left-hand sides.
Collect examples of higher-order rewrite systems for testing the algorithms.
References
van der Wulp. Notes for the design of a reusable higher-order conditional rewriting library. 2009.
van Raamsdonk. On Termination of Higher-Order Rewriting. RTA 2001, Lecture Notes in Computer Science, vol. 2051, pp. 261–275. Springer, 2001.