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!