4

I'm trying to understand Active Patterns, so I'm playing around with FizzBuzz:

let (|Fizz|_|) i = if i % 3 = 0 then Some Fizz else None
let (|Buzz|_|) i = if i % 5 = 0 then Some Buzz else None
let (|FizzBuzz|_|) i = if i % 5 = 0 && i % 3 = 0 then Some FizzBuzz else None

let findMatch = function
    | Some Fizz -> "Fizz"
    | Some Buzz -> "Buzz"
    | Some FizzBuzz -> "FizzBuzz"
    | _ -> ""

let fizzBuzz = seq {for i in 0 .. 100 -> Some i}
                |> Seq.map (fun i -> i, findMatch i)

Is this basically the right approach, or is there a better way to use Active Patterns here? Shouldn't I be able to make findMatch take an int instead of int option?

cdlane
  • 40,441
  • 5
  • 32
  • 81
Nick Heiner
  • 119,074
  • 188
  • 476
  • 699

3 Answers3

10

Daniel's first solution can be simplified, because you don't actually need to define a separate active pattern for FizzBuzz. The case can be described as both Fizz and Buzz matching, which can be nicely expressed in the pattern language:

let findMatch = function 
  | Fizz & Buzz -> "FizzBuzz" 
  | Fizz -> "Fizz" 
  | Buzz -> "Buzz" 
  | _ -> "" 

let fizzBuzz = [ for i in 0 .. 100 -> findMatch i ]

The pattern Fizz & Buzz matches if both Fizz and Buzz match. This relies on the fact that the pattern is matched first, so the order is relevant in this case. I also shortened your last line a little to a style that I prefer and is a bit shorter (but opinions vary).

Alternatively, if you don't want to define too many single-purpose active patterns, you could also write a parameterized active pattern that tests whether an input is divisible by any specified number:

let (|DivisibleBy|_|) by n = if n%by=0 then Some DivisibleBy else None

let findMatch = function 
  | DivisibleBy 3 & DivisibleBy 5 -> "FizzBuzz" 
  | DivisibleBy 3 -> "Fizz" 
  | DivisibleBy 5 -> "Buzz" 
  | _ -> "" 
ildjarn
  • 62,044
  • 9
  • 127
  • 211
Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
8

Your findMatch function should be:

let findMatch = function
    | FizzBuzz -> "FizzBuzz" (* should be first, as pad pointed out *)
    | Fizz -> "Fizz"
    | Buzz -> "Buzz"
    | _ -> ""

You can rewrite the last few lines:

let fizzBuzz = Seq.init 100 (fun i -> i, findMatch i)

Your active patterns are fine. One alternative is to use a complete active pattern:

let (|Fizz|Buzz|FizzBuzz|Num|) i = 
    match i % 3, i % 5 with
    | 0, 0 -> FizzBuzz
    | 0, _ -> Fizz
    | _, 0 -> Buzz
    | _ -> Num i
Daniel
  • 47,404
  • 11
  • 101
  • 179
3

Remove (unnecessary) Some so that findMatch function takes int as its parameter:

let findMatch = function
    | FizzBuzz -> "FizzBuzz" (* Should be the first pattern *)
    | Fizz -> "Fizz"
    | Buzz -> "Buzz"
    | _ -> ""

let fizzBuzz = seq { for i in 0..100 -> i, findMatch i }
pad
  • 41,040
  • 7
  • 92
  • 166