cmeeren
cmeeren

Reputation: 4210

Negative pattern match

I have the following code (`IgnoreCase is an active pattern I have defined elsewhere):

match myType with
| {Field1 = IgnoreCase "invalid"} -> None
| {Field2 = Some f2
   Field3 = Some f3
   Field4 = None | Some (0 | 1 | 2)}
    -> Some (f2, f3)
| _ -> None

As you can see, Field1 has a blacklisted value and Field4 has whitelisted values (so do Field2 and Field3 in the sense that they must be Some). IMHO it would look slightly cleaner if I could do all checks in the same case, i.e. do the Field1 = IgnoreCase "invalid" match together with the other matches using e.g. Field1 <> IgnoreCase "invalid", but that particular example does not compile. I know about guards, but that does not seem cleaner than the original solution.

Is it possible to do "negative" (logical NOT) pattern matches in the sense that a value should not match some other value, without using guards?

Upvotes: 2

Views: 630

Answers (3)

kvb
kvb

Reputation: 55195

As far as I know, there's no good way to do this. You can see the supported patterns here and verify that there's no construct for negation. One reason for the lack of such a feature might be that it would interact strangely with other pattern matching features (e.g. what would it mean to introduce an identifier with an as pattern inside of a negative context?). And this is not something that you could implement in a general fashion yourself via an active pattern, because an active pattern receives a value, not a pattern expression.

Upvotes: 3

TheQuickBrownFox
TheQuickBrownFox

Reputation: 10624

You could define an active pattern called Not:

let (|Not|_|) a b = if a <> b then Some () else None

Then you can use it inside the pattern match:

match myType with
| {Field1 = Not "invalid"
   Field2 = Some f2
   Field3 = Some f3
   Field4 = None | Some (0 | 1 | 2)}
   -> Some (f2, f3)
| _ -> None

A limitation of this active pattern is that it must take a literal value, not a sub-pattern. Active patterns aren't composable like the built-in patterns. However, that's also a potential advantage because you can pass it identifiers as values instead of binding a new value to the identifier:

let invalidString = "invalid"

match myType with
| {Field1 = Not invalidString
...

Upvotes: 2

robkuz
robkuz

Reputation: 9934

I would use active patterns like this

let (|Check|_|) x =
    let = { Field2 = f2; Field3 = f3; Field4 = f4 }
    let check = 
        f4 = None ||
        f4 = Some 1 ||
        f4 = Some 2 ||
        f4 = Some 3
    if check then Some (f2, f3) else None

    match myType with
    | {Field1 = "invalid"} -> None
    | Check (x, y) -> Some (x, y)
    | _ -> None

Upvotes: 1

Related Questions