Hello,
Here's a thought. How about making it possible to declare that
some operator distributes over some other operator.
The point of this would be to obviate special-case code
that looks for lists, matrices, sums, etc.
E.g. trigrat at present triggers an error if applied to a matrix;
instead of patching the code, we could just make a declaration.
Here's an example session.
(%i1) load ("./distributes_over.mac");
(%o1) ./distributes_over.mac
(%i2) declare (foo distributes_over bar);
(%o2) foo distributes_over bar
(%i3) foo (bar (x, y, z));
(%o3) bar(foo(x), foo(y), foo(z))
(%i4) declare (foo distributes_over [matrix, set, "["]);
(%o4) [foo distributes_over matrix, foo distributes_over set,
foo distributes_over []
(%i5) foo (matrix ([1, 2], [a, b]));
[ foo(1) foo(2) ]
(%o5) [ ]
[ foo(a) foo(b) ]
(%i6) foo ({a, b, c});
(%o6) {foo(a), foo(b), foo(c)}
(%i7) foo ([a, b, c]);
(%o7) [foo(a), foo(b), foo(c)]
(%i8) declare (foo distributes_over ["+", "*"]);
(%o8) [foo distributes_over +, foo distributes_over *]
(%i9) foo (a + b + c);
(%o9) foo(c) + foo(b) + foo(a)
(%i10) foo (a * b * c);
(%o10) foo(a) foo(b) foo(c)
(%i11) foo (a*b + c*d);
(%o11) foo(c) foo(d) + foo(a) foo(b)
Notes.
(1) distributes_over is implemented as a simplification.
Each left-hand (outer) operator gets a rule.
Right-hand (inner) operators are stored as a property.
(2) distributes_over as implemented below doesn't know
what to do if the left-hand (outer) operator takes multiple
arguments. I guess the matchdeclare and tellsimp need
to be more complicated.
(3) There is some code in share to declare operators
as "threadable". I think that existing code can be improved
in 2 ways. I think "distributes_over" is a more widely understood
name. Also I think it is better to allow specifying the set of
operators over which to distribute.
(4) declare(f, distributes_over(g)) would be more like the
existing declaration syntax, but I've never shared Maxima's
hankering for paired arguments. Just put all the arguments
into one predicate expression.
(5) The code as it stands doesn't have any effect on
user-defined infix, prefix, or postfix operators.
I don't know why that is. I'll look into it some more.
(6) I guess there should be corresponding
featurep(f distributes_over g) and remove(f distributes_over g)
but I haven't gotten to that yet.
(7) Loopy declarations such as declare(foo distributes_over bar)
and declare(bar distributes_over foo) cause stack overflow.
I guess I'll have to fix that.
FWIW
Robert Dodier
PS. Here's the code, as it stands.
--------------- distributes_over.mac
fix ("distributes_over");
load ("distributes_over.lisp");
declare_distributes_over (f, g) :=
if listp (f)
then map (lambda ([f1], declare_distributes_over (f1, g)), f)
elseif listp (g)
then map (lambda ([g1], declare_distributes_over (f, g1)), g)
else
(get (f, "distributes_over"),
if %% = false
then
(put (f, [g], "distributes_over"),
buildq ([f], lambda ([e], not atom (e) and member (op (e),
get (f, "distributes_over")))),
apply (matchdeclare, [g_expr, %%]),
buildq ([f, g], [f (g_expr), apply (op (g_expr), map (f,
args (g_expr)))]),
apply (tellsimp, %%))
elseif not member (g, %%)
then put (f, cons (g, %%), "distributes_over"),
f distributes_over g);
--------------- distributes_over.lisp
(let (($declare-original (get '$declare 'mfexpr*)))
(defmspec $declare (expr)
(let ((x (cadr expr)))
(if (and (not (atom x)) (eq (caar x) '$distributes_over))
(meval `(($declare_distributes_over) ,@(cdr x)))
(apply $declare-original (list expr))))))