The pattern-matching system in Grace1 allows only actual literals to be used unadorned as patterns:
1 Patterns as Objects in Grace. Michael Homer, James Noble, Kim B. Bruce, Andrew P. Black, David J. Pearce. In Dynamic Language Symposium (DLS), 2012. https://doi.org/10.1145/2480360.2384581
method fact(x) {
match (x)
case { 0 -> 1 }
case { n -> n * fact(n - 1) }
}
Numbers and strings are "autozygotic" values and match themselves, and their literals can be written directly as patterns to do that. The n above is a binding case, and in fact exactly the syntax for a lambda block of one untyped parameter.
Other patterns can be written following a colon: {x : A -> ...} will bind x if the value of A matches the target, either because that value is a literal, or because A is some other kind of pattern. This also overlaps exactly the syntax for a lambda block of one typed parameter:
match (x)
case { x : A -> 1 }
case { y : String -> y.size }
This was a deliberate choice to allow the same mechanic to build up from simple cases to more complex user-defined patterns, and from different starting points (e.g. introduction through simple typecase or literals). Later work explored this point further. The binding and usage cases are syntactically distinct, so typos or semantic errors are no harder to detect than elsewhere.
At run time, the name of a type refers to a pattern that matches instances of that type, so the type namespace is a subset of the ordinary namespace and there is no additional ambiguity. Types are presumably statically known to an analysis tool, so it will be able to distinguish a type and another kind of pattern in this position if required (but all patterns identify the type of the value they will bind anyway).
There is also a second escape hatch: a pattern expression can be surrounded by parentheses to force it to be evaluated, so { (a) -> ...} will have the same effect as { _ : a -> ...}. There doesn't seem to be significant additional value to this, and I don't think I'd bother with it again.
There was an additional kind of binding pattern in the original design from that paper, and it did cause some issues that led to its being removed from later versions. That was mostly because the implementation overhead wasn't worthwhile when in practice the revealed preference of users (including me!) was to avoid using it in favour of alternatives. That preference may have come in part because of difficulty disambiguating uses and mentions as a human, but not so much because of issues of static analysis or typos.
As originally proposed, "function call" syntax in this position, like { A(B("x")) -> ... }, would also be interpreted as a pattern, and so would the "arguments" to allow complex nested destructuring patterns. This did create an ambiguity for the user, if not for the compiler. Although it looked like a method call, it wouldn't actually be one, but rather each "function name" named a pattern (i.e. a noun, not a verb), with the "arguments" also patterns to be combined later.
case { Operator("+", ASTNumber(0), y) -> "Just {y}" }
Here Operator names a pattern that matches an object and extracts three subvalues, each of which is then matched against the corresponding subpattern. The y subpattern is a binding instance, the "+" is a literal, and ASTNumber(0) is another pattern with its own subpattern. This is where there is an ambiguity: ASTNumber(0) is not calling ASTNumber with argument 0, but rather identifying two pattern objects ASTNumber and 0 which the matching system will combine (and _ : ASTNumber(0) would be the version that invoked the single-argument function). y, as an unadorned name, was again not the value of an existing y but a binding instance that would create a variable y in the match scope.
This binding syntax turned out not to get much use: because it's an object-oriented language, people preferred to match and then use the object's accessors to extract data from it afterwards. The semantics is also quite complex, and for example ASTNumber(x) and ASTNumber((x)) have different meanings here but not outside of the pattern context. Users did experience confusion about the exact semantics and either avoided it because of that, or weren't interested in it anyway.
The semantics of nested bindings were also complicated around pattern combinators: the pattern A(x) & B(y) would bind names x and y (if both A and B matched), but A(x) | B(y) would bind neither, because which side had matched was not known statically. The pattern system was also discussed in more depth in Chapter 4 of my thesis, and Section 4.7.7 discusses an alternative approach that unrolls all match-case blocks in a way that would address some of these binding cases, but was found too complex to type and to understand as a user.
Later implementations did not include the binding subpatterns at all, and would fully-evaluate the pattern expression as an ordinary method call. That is, programmers preferred to write, and later implementations only supported, writing the above example as:
case { nd : Operator("+", ASTNumber(0), Number) -> "Just {nd.rhs}" }
where Operator("+", ASTNumber(0), Any) is a single call to a method that combines its three arguments somehow to create a pattern; those arguments may or may not be patterns. There is no privileged kind of destructuring, and no way to bind a new name in the middle. The semantic and syntactic complexity of destructuring binding may have been part of why users tended not to use it, but preference for using the matched value "as" an object seemed to be the commonly articulated reason.
On the implementation side, given the very limited usage, supporting binding patterns was not worth the complexity it imposed on the back-end code. All of the use cases other than binding names were served equally well, or better, by evaluating the pattern expressions as ordinary expressions.
In a language that more ubiquitously used pattern-matching, those concerns might be less impactful, but in this one where there was an easy alternative to access properties of the matched value it appeared that, regardless of experience, programmers preferred to avoid the versions that used nested binding patterns. The "top-level" bindings are syntactically and semantically unambiguous because only actual literals and expressions in "type position" are treated as patterns, and these do not seem to cause any issues with readability or understanding.
(x, y) = (1, 2)which assigns, orlet (x, y) = (1, 2);which binds... but I am not even sure there's a syntax allowing a mix of binding & assigning. $\endgroup$