calc: Composing Patterns in Rewrite Rules
11.11.6 Composing Patterns in Rewrite Rules
-------------------------------------------
There are three operators, ‘&&&’, ‘|||’, and ‘!!!’, that combine rewrite
patterns to make larger patterns. The combinations are “and,” “or,” and
“not,” respectively, and these operators are the pattern equivalents of
‘&&’, ‘||’ and ‘!’ (which operate on zero-or-nonzero logical values).
Note that ‘&&&’, ‘|||’, and ‘!!!’ are left in symbolic form by all
regular Calc features; they have special meaning only in the context of
rewrite rule patterns.
The pattern ‘P1 &&& P2’ matches anything that matches both P1 and P2.
One especially useful case is when one of P1 or P2 is a meta-variable.
For example, here is a rule that operates on error forms:
f(x &&& a +/- b, x) := g(x)
This does the same thing, but is arguably simpler than, the rule
f(a +/- b, a +/- b) := g(a +/- b)
Here’s another interesting example:
ends(cons(a, x) &&& rcons(y, b)) := [a, b]
which effectively clips out the middle of a vector leaving just the
first and last elements. This rule will change a one-element vector
‘[a]’ to ‘[a, a]’. The similar rule
ends(cons(a, rcons(y, b))) := [a, b]
would do the same thing except that it would fail to match a one-element
vector.
The pattern ‘P1 ||| P2’ matches anything that matches either P1 or
P2. Calc first tries matching against P1; if that fails, it goes on to
try P2.
A simple example of ‘|||’ is
curve(inf ||| -inf) := 0
which converts both ‘curve(inf)’ and ‘curve(-inf)’ to zero.
Here is a larger example:
log(a, b) ||| (ln(a) :: let(b := e)) := mylog(a, b)
This matches both generalized and natural logarithms in a single
rule. Note that the ‘::’ term must be enclosed in parentheses because
that operator has lower precedence than ‘|||’ or ‘:=’.
(In practice this rule would probably include a third alternative,
omitted here for brevity, to take care of ‘log10’.)
While Calc generally treats interior conditions exactly the same as
conditions on the outside of a rule, it does guarantee that if all the
variables in the condition are special names like ‘e’, or already bound
in the pattern to which the condition is attached (say, if ‘a’ had
appeared in this condition), then Calc will process this condition right
after matching the pattern to the left of the ‘::’. Thus, we know that
‘b’ will be bound to ‘e’ only if the ‘ln’ branch of the ‘|||’ was taken.
Note that this rule was careful to bind the same set of
meta-variables on both sides of the ‘|||’. Calc does not check this,
but if you bind a certain meta-variable only in one branch and then use
that meta-variable elsewhere in the rule, results are unpredictable:
f(a,b) ||| g(b) := h(a,b)
Here if the pattern matches ‘g(17)’, Calc makes no promises about the
value that will be substituted for ‘a’ on the righthand side.
The pattern ‘!!! PAT’ matches anything that does not match PAT. Any
meta-variables that are bound while matching PAT remain unbound outside
of PAT.
For example,
f(x &&& !!! a +/- b, !!![]) := g(x)
converts ‘f’ whose first argument is anything _except_ an error form,
and whose second argument is not the empty vector, into a similar call
to ‘g’ (but without the second argument).
If we know that the second argument will be a vector (empty or not),
then an equivalent rule would be:
f(x, y) := g(x) :: typeof(x) != 7 :: vlen(y) > 0
where of course 7 is the ‘typeof’ code for error forms. Another final
condition, that works for any kind of ‘y’, would be ‘!istrue(y == [])’.
(The ‘istrue’ function returns an explicit 0 if its argument was left in
symbolic form; plain ‘!(y == [])’ or ‘y != []’ would not work to replace
‘!!![]’ since these would be left unsimplified, and thus cause the rule
to fail, if ‘y’ was something like a variable name.)
It is possible for a ‘!!!’ to refer to meta-variables bound elsewhere
in the pattern. For example,
f(a, !!!a) := g(a)
matches any call to ‘f’ with different arguments, changing this to ‘g’
with only the first argument.
If a function call is to be matched and one of the argument patterns
contains a ‘!!!’ somewhere inside it, that argument will be matched
last. Thus
f(!!!a, a) := g(a)
will be careful to bind ‘a’ to the second argument of ‘f’ before testing
the first argument. If Calc had tried to match the first argument of
‘f’ first, the results would have been disastrous: since ‘a’ was unbound
so far, the pattern ‘a’ would have matched anything at all, and the
pattern ‘!!!a’ therefore would _not_ have matched anything at all!