You want active patterns:
let (|IsRoyalFlush|_|) hand =
if isRoyalFlush hand then Some () else None
let (|IsStraightFlush|_|) hand =
if isStraightFlush hand then Some() else None
// etc.
match hand with
| IsRoyalFlush -> 9
| IsStraightFlush -> 8
// etc.
Or better, pull out all that common code into a simple active-pattern builder:
let toActivePattern pred x =
if pred x then Some () else None
let (|IsRoyalFlush|_|) = isRoyalFlush |> toActivePattern
let (|IsStraightFlush|_|) = isStraightFlush |> toActivePattern
// etc.
match hand with
| IsRoyalFlush -> 9
| IsStraightFlush -> 8
// etc.
If you don't understand how that second example works, leave a comment and I'll go into more depth.
Composing active patterns together
Since active patterns are just functions, you can use standard function composition to join them together. Well, almost standard function composition. Normal function composition with the >>
operator means "take the result of function 1, and use it as the input to function 2". But here, function 1 and function 2 both return Some ()
, but take an int; you can't pass the output of 1 into the input of 2, since they aren't compatible types. But what we want is actually to pass the same input to both functions, and combine their output.
So instead of using normal function composition, we'll define our own function that takes two active patterns, and returns Some ()
if both patterns match the input:
let matchesBoth pattern1 pattern2 x =
match pattern1 x with
| None -> None
| Some _ -> pattern2 x
And while we're at it, let's define a custom operator so you can see how that works. This matchesBoth
function works very much like the &&
operator, in that it will return Some ()
only if both patterns would return Some ()
for any given input x
. We shouldn't overload the &&
operator to take a different type, so let's create a custom operator that looks like &&
, but reminds us that it's combining two active patterns. If our operator looks like |&&|
, that should be perfect. So let's create it:
let (|&&|) = matchesBoth
That's it! Now we can do something like:
let (|Div3|_|) n =
if n % 3 = 0 then Some () else None
let (|Div5|_|) n =
if n % 5 = 0 then Some () else None
let (|Div15|_|) = (|Div3|_|) |&&| (|Div5|_|)
let fizzbuzz n =
match n with
| Div15 -> "FizzBuzz"
| Div5 -> "Buzz"
| Div3 -> "Fizz"
| _ -> string n
fizzbuzz 30 // Result: "FizzBuzz"
fizzbuzz 31 // Result: "31"
Or, for your example:
let (|IsStraightFlush|_|) = (|IsStraight|_|) |&&| (|IsFlush|_|)