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*True *)
MatchQ[g[1], g[a_]] (* True*True *)
MatchQ[g[1], g[g[1]]] (* True*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}} *)