Note: You might want to consider simply using NCExpand[a_] := Distribute[a, Plus, NonCommutativeMultiply]
. That said, the following provides a useful way of making NonCommutativeMultiply
behave like Times
with an identity (which is beneficial even outside of this problem), and also answers the original question.
So, if you want to mirror how e.g. Times
works, then what you want to do is exploit the OneIdentity
attribute (which NonCommutativeMultiply
already has), Default
values and Optional
patterns, and implement an identity value for NonCommutativeMultiply
.
If f
has the attribute OneIdentity
, that means that f[x]
, f[f[x]]
, etc. will match x
.
Optional
patterns match a pattern to a provided default value if the expression does not appear. But there are two ways to provide a default value: either inline or via a value that's globally associated with the enclosing head. For example, if we have f[a_:0, b_] := {a,b}
, then f[2]
will be {0,2}
thanks to the inline Optional
pattern a_:0
. But we could also use f[a_., b_]
(a_.
is Optional[a]
) together with a separate evaluation of Default[f] = 0
to get the same result.
This works for pattern matching too: e.g. Default[Times]
is 1
, and f[Times[x_.,3]] := x
means that f[3 y]
gives y
, while f[3]
gives 1
. The OneIdentity
attribute means that 3
matches Times[3]
which matches Times[x_.,3]
.
To be honest, I'm not 100% sure why b+c
doesn't then match NonCommutativeMultiply[a___,b_+c_,d___]
. It seems OneIdentity
only meshes well with Optional
patterns. However, it does match NonCommutativeMultiply[a_., b + c, d_.]
.
Here's a gotcha to watch out for: the Default
value must be set before any definitions involving default patterns are evaluated. I suspect Optional
is partially magical.
Making NonCommutativeMultiply
behave well
We then also need to actually make our Default
value actually behave like an identity for NonCommutativeMultiply
. To do this, we add a definition using the intercept trick.
We also need to make NonCommutativeMultiply[a]
be a
and NonCommutativeMultiply[]
be the default value. Due to, I suspect (but possibly not), the magic of Optional
, we need to temporarily clear NonCommutativeMultiply
's attributes while setting that definition.
So, putting it all together:
Unprotect[NonCommutativeMultiply];
Default[NonCommutativeMultiply] = 1; (* or whatever value you'd like here *)
(* Set unary and nullary behavior: *)
Block[{a = Attributes[NonCommutativeMultiply]},
Attributes[NonCommutativeMultiply] = {};
NonCommutativeMultiply[a_.] := a;
Attributes[NonCommutativeMultiply] = a;
];
(* Make Default[NonCommutativeMultiply] behave as an identity
for NonCommutativeMultiply and avoid infinite recursion
via the intercept trick: *)
NCMIntercept = True;
NonCommutativeMultiply[a_, b__] :=
Block[{NCMIntercept = False},
NonCommutativeMultiply @@ DeleteCases[{a,b}, Default[NonCommutativeMultiply]]
] /; NCMIntercept
Protect[NonCommutativeMultiply, NCMIntercept];
NCExpand[a_. ** (b_ + c_) ** d_.] := a ** b ** d + a ** c ** d
(Note that while NCExpand[b + c]
works as expected, NCExpand[b]
is still undefined! Another definition (NCExpand[a_] := a
) is needed.)
It is possible to do this a bit differently if you want NonCommutativeMultiply[a]
to not evaluate further (the default behavior), perhaps by using Default[NonCommutativeMultiply] := Sequence[]
. I haven't explored this though.
Hope this helps! :)
f[a___,b_+c_,d___]:=f[a,b,d]+f[a,c,d]
. For a tutorial, see this. $\endgroup$NCExpand[b+c]
it will just returnNCExpand[b+c]
. I was wondering if you could somehow include this case in the definition ofNCExpand[]
without requiring a separate definitionNCExpand[b_+c_]:=NCExpand[b]+NCExpand[c]
. $\endgroup$