views:

156

answers:

1

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).

+3  A: 

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.

Chris Smith
so then the patterns return booleans allowing to not have to worry about the nullable type, while also making the code more condensed...not sure if that makes sense, but thanks, that helped.
Actually, the partial active patterns can return any form of data. For example, the IsBet pattern can return the bet amount.match player11 with| IsBet amount with amount > 100 -> printfn "Bet more than $100!"
Chris Smith