overloading F# active patterns

649 Views Asked by At

I am fairly new to F# and active patterns, and I ran across an anomoly that I can't explain.

module Eval =
let (|Bet|Pass|) (test:BetChoice) =
    match test with
        | BetChoice.Bet -> Bet
        | BetChoice.Pass -> Pass

let (|NoBet|Bet|Pass|) (test:Nullable<BetChoice>) : Choice<unit, unit, unit> =
    match test.HasValue with
        | true -> match test.Value with 
                      | BetChoice.Bet -> Bet
                      | BetChoice.Pass -> Pass
        | false -> NoBet

let FlipByWinner ((value:int), (awins:bool)) =
    match awins with
    | true -> (value, -value)
    | false -> (-value, value)

let Evaluation (awins:bool) (player11:BetChoice) (player21:BetChoice) (player12:Nullable<BetChoice>) =
     match player11 with
     | Pass -> match player21 with
               | Pass -> FlipByWinner(1, awins)
               | Bet-> match player12 with
                       | Bet -> FlipByWinner(2, awins)
                       | Pass -> FlipByWinner(1, false)
                       | NoBet -> raise (System.ArgumentException("invalid strategy"))
     | Bet ->  match player21 with
               | Bet -> FlipByWinner (2, awins)
               | Pass -> FlipByWinner (1, false)

This doesn't compile. With a minor tweak I can make it work as intended, but the fact that I don't know exactly what is going on makes me a little nervous... the second pattern can get renamed to "(|NoBet|Bet1|Pass1|)" and it's associated patterns changed throughout the code, then it works, but I don't really get why this has a type mismatch exception.

Also is there a good way of dealing with 2 active patterns that are almost identical, but not quite? seems like there should be a way to factor the common stuff together. (as a side note, it looks like the indenting got messed up in the copy/paste, this is all part of the module Eval properly).

1

There are 1 best solutions below

2
On BEST ANSWER

Yes,the first problem makes perfect sense. You cannot have two active pattern tags with the same name in the same namespace. This has to do with how the F# compiler actually generates active pattern names. If you look in reflector, the code generated for:

type Alpha = Foo | Bar
let (|Foo|Bar|) = ...

is very different, even though conceptually active patterns and discriminated unions are quite similar concepts.

Anyways, the question you are after is what to do if you have two sets of active patterns that are mostly similar / albiet different. What I would urge you to do is to use partial active patterns. See this blog post.

In the example, it looks like you want something like:

let (|IsBet|_|) = ...
let (|IsPass|_|) = ...

That way you can pattern match against both player 11 and player 21 at the same time, e.g.:

match player11, player21 with
| IsPass & IsPass -> ...
| IsPass & IsBet  -> ...
| IsBet  & IsPass -> ...
| IsBet  & IsBet  -> ...

This should go a long way towards cleaning up the code.