Why doesn't the < operator work in a pattern matching expression?

Just encountered the issue today of using < in a record pattern match. Ex.

let fn x =
  match x with
  | { Foo < 0 } -> bar
  ...

There’s an old question on SO, but while there’s an answer provided, there is no explanation as to why this is the case.

Any thoughts here?

You need to use a when expression, e.g.

let fn x =
    match x with
    | { Foo = bar } when bar < 0 -> bar
    | _ -> failwith "bar >= 0"

As to why this is the case, it’s due first to the fact that the = is an equality / assignment. If you provide a value, it checks that the value matches. If a label is provided, the label is assigned the value. Allowing <, >, etc. would break this operation.

Secondly, it’s due to F#'s history as an OCaml for .NET. Since OCaml uses when to perform additional conditional checks, so does F#. The pattern match is intended to exactly match a data structure. The when allows you to use what you matched or external values in considering a result. For example, suppose you accepted an x and a y and wanted to return bar only if y < 0:

let fn x y =
    match x with
    | { Foo = bar } when y < 0 -> bar
    | { Foo = bar } -> failwith "y was >= 0"

In addition to what @panesofglass wrote -

I think there may be multiple issues at play here. One issue is possibly confusion about | { Foo = 0 } in a match. The = at that point is not a comparison - it’s part of a “record pattern”. If you look at the Pattern Matching docs, you’ll see that each case of a pattern match is | pattern [ when condition ] -> result-expression : so you match a pattern (and optionally a condition, which @panesofglass showed nicely).

One type of pattern is a “Record pattern”, which is { identifierN = patternN } - so you setup an identifier, and a “sub pattern” that is matching. With {Foo = 0}, this is a record pattern containing a “Constant pattern” of 0, not a comparison for equality to 0.

This is more obvious if you nest even more patterns - for example, you can nest an OR pattern:

match foo with
| { Foo = 10|12} -> doSomething()

Here, the pattern is an OR pattern, with each sub pattern being a constant pattern.

If you go through the full list of patterns, you’ll see that no pattern has < as part of it’s syntax - there is no “comparison pattern”. Instead, you use a “Variable pattern” and then the optional conditional, ie: | { Foo = bar } when bar < 0.

Wow! I did not know about the nested |. That’s pretty cool!

Thank you to both of you guys. It is much much clearer now! Funny that even in the case of Foo = 10 that it’s not really even checking for equality. It’s using a constant pattern against the identifier to check equality.

Noiiice, now I learned something too!