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