Go to the first, previous, next, last section, table of contents.

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 disasterous: 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!

Go to the first, previous, next, last section, table of contents.