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?

1 Like

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"
3 Likes

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.

9 Likes

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

4 Likes

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.

1 Like

Noiiice, now I learned something too!