proposal to modify op and args



On 5/7/06, Robert Dodier <robert.dodier at gmail.com> wrote:
>
> At present the functions op and args barf on atoms.
> I propose that op and args return false when the argument is an atom.
>
> This change is intended to obviate the need to always test whether
> the argument is an atom before calling op or args.


This does indeed make one use-case easier, namely a dispatch of the form:

    block( [op: op(expr)],
         if op=false then...        // 3, 3.0, 3.0b0, x
                                                  // but NOT 1/3, %pi/2,
x[1]
         elseif op="+" then ...
         ...)

However, it also makes it much easier to incorrectly assume that an
expression *has* an operator when it doesn't, and to propagate nonsense
further into the program.  Errors are a *good* thing in cases like this.
For that matter, checking whether an expression is an atom is usually not
the right thing -- you want to check whether it is a symbolic constant or
variable, which may be subscripted, so the above code would become:

    block( [op],
        if mapatom(expr) then ...            // 3, 3.0, 3.0b0, 1/3, %pi, x,
x[1]
        elseif (op:op(expr))="+" then ...
        ...)

or in more sophisticated code, probably something like

    block( [op],
        if numberp(expr) then  ...            // 3, 3.0, 3.0b0, 1/3
        elseif constantp(expr) then ...     // %pi, %pi/2
        elseif mapatom(expr) then ...     // x, x[1]
        ...)

Alternatively, if you only want to handle a few operators, it might be
cleaner to define:

     plusexprp(expr):= not(atom(expr)) and op(expr)="+"

and use those.  Yes, there is a slight performance penalty....

The main ugly thing in all this is the name "mapatom" (meaning something
that 'map' treats as atomic).

Oh, and by the way, it is probably a bad idea to use "op" in code in the
first place, since its behavior is controlled by the global inpart flag.
Much better to define inop(ex):=block([inflag:true],op(ex)), which is both
more consistent and much more efficient.  By the way, did you know that
op(3/4)=inop(3/4)="//"?  Surprise!  If we were designing from scratch, I
would *not* have op(x[1])=x or op(3/4)=/, but I think those are important
cases to keep backwards-compatible.

In all, it seems to me that it would be a classic design error to have op do
anything other than give an error when it is passed inappropriate arguments=
.

          -s