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 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 *)
Block[{a = Attributes[NonCommutativeMultiply]},
Attributes[NonCommutativeMultiply] = {};
NonCommutativeMultiply[a_.] := a;
Attributes[NonCommutativeMultiply] = a;
];
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! :)