How to handle pattern matching on constants

I think const makes a pretty poor pattern synonym. For example, you can't handle binding. Also, the intent is quite unclear. Constants are primarily used as values, but they also double as a pattern synonym -- which is like a value, but different, for the reasons I outlined in the OP (it behaves differently in various cases, exposes details that would not otherwise be exposed, etc).

Looking at the wiki page on Haskell's pattern synonyms, they seem very different from consts. Also (unsurprisingly) they seem to address the shortcomings I listed above. For example, the intention is quite clear: you declare them with a pattern keyword:

   pattern Arrow t1 t2 = App "->" [t1, t2]

Similarly, as you can include bindings (the t1 and t2 above). Also, when you choose to make use of a pattern synonym, it seems like you must (or can) be very explicit about importing it:

   module Foo (pattern Arrow) where ...

Now, there is SOME intersection. For example, you can define pattern synonyms that also function as constructor functions or as expressions (and, presumably, thus something like a constant today, if the constructor takes no arguments). But in all these cases, you prefix the definition with pattern, making it very clear that you are defining something that plays multiple roles.

So I guess my feeling is: pattern synonyms seem like they could be useful! I'd consider implementing them someday (perhaps as an alternative to overridable patterns). But if we want pattern synonyms, let's do it right, and not abuse poor constants.

I don't know what this really means. I think of patterns as a convenient notation for specifying "test and extract value" operations. So, Some(<pattern>) compiles to "test that the variant is Some and extract the value". We then recursively apply <pattern> to that value. Sometimes there are no values to extract: if you match a nullary enum variant like None, it just means "test that the variant is None". So, along these lines, I simply see the meaning of a const pattern like FOO to be "test that the value is PartialEq to FOO and extract no values". That is to say, this is not "treating constants as something other than a pattern", it's "defining what it means for a const to appear in a pattern".

It is worth reiterating that floats behave exactly this way today. That is, a value of -0 will match a pattern of 0. Similarly, we give errors if you match on NaN -- any of the various possible NaN interpretations -- presumably because the arm would never match. But under the current interpretation of constants in patterns, there is no way to make a user-defined type that acts this way! I could write a struct Float { mantissa, exponent } that was identical to f64 in every way from the point of view of expressions, but matching on constants like const ZERO: Float = ... and const NEG_ZERO: Float = ... would behave differently, as well as matching on const NAN: Float.

So, to clarify: do you think that matching on NaN should succeed if it happens to be the same bitpattern for NaN, but fail if it is not? Now, it's an error today, so you can dodge the question, but then you have to wonder WHY it is an error -- that only makes sense if you assume a PartialEq semantics. And, of course, why aren't user-defined abstractions afforded the same courtesy that float is.

2 Likes