Pattern matching: Times[a_] vs Times[a__]
The observed behaviour will appear in any expression whose symbolic head has the attribute Flat
.
Under normal circumstances, with no attributes in play, we see the usual expected behaviour:
MatchQ[f[1], f[1]] (* True *)
MatchQ[f[1], f[a_]] (* True *)
MatchQ[f[1], f[f[1]]] (* False *)
MatchQ[f[1], f[f[a_]]] (* False *)
MatchQ[f[1, 2, 3], f[a_]] (* False *)
But when we introduce the Flat
attribute, our normal intuition no longer holds:
SetAttributes[g, Flat]
MatchQ[g[1], g[1]] (* True *)
MatchQ[g[1], g[a_]] (* True *)
MatchQ[g[1], g[g[1]]] (* True *)
MatchQ[g[1], g[g[a_]]] (* True *)
MatchQ[g[1, 2, 3], g[a_]] (* True *)
What is happening?
The purpose of Flat
is to flatten out any nested expressions. That is, g[g[1, 2, 3]]
is to be treated as equivalent to g[1, 2, 3]
. The key point is that this equivalence works both ways. So when we ask whether g[1, 2, 3]
matches the pattern g[a_]
, then this is equivalent to asking whether g[g[1, 2, 3]]
matches g[a_]
. Which of course it does. That is why the MatchQ
expression in the question returns True
. As does MatchQ[g[g[g[g[g[1, 2, 3]]]]], g[a_]]
Can we turn it off?
A simple way to perform the exact match requested by the question is to temporarily remove the Flat
attribute from Times
. Block
will strip Times
of all of its attributes:
Block[{Times}
, MatchQ[
HoldComplete[Times[3, 2, 2]]
, HoldComplete[Times[a_]]
]
]
(* False *)
If, for some reason, the application is such that we wish to retain the other attributes of Times
, we can use Internal`InheritedBlock
:
Attributes[Times]
(* {Flat, Listable, NumericFunction, OneIdentity, Orderless, Protected} *)
Internal`InheritedBlock[{Times}
, Unprotect[Times]
; ClearAttributes[Times, Flat]
; Protect[Times]
; { MatchQ[
HoldComplete[Times[3, 2, 2]]
, HoldComplete[Times[a_]]
]
, Attributes[Times]
}
]
(* {False, {Listable, NumericFunction, OneIdentity, Orderless, Protected}} *)
Times
has attributes Flat
, Orderless
, and OneIdentity
to cater for Associativity and Commutativity.
These attributes affect patterns; in your case to ensure that x_.y_.z_
not only matches the pattern x
, but also y
and z
. This is in line with the commutative property of multiplication.
As WReach says in his answer, the issue is with attributes. Another way to turn off the Flat
attribute in pattern matching is to use Verbatim
:
MatchQ[HoldComplete[Times[3, 2, 2]], HoldComplete[Verbatim[Times][_]]]
False