I'm learning F#, and have challenged myself to write a good functional implementation of FizzBuzzWoof. (The difference from "standard" FizzBuzz is that any number that either is divisible by or contains the appropriate number must be replaced by the appropriate string of text: 3 = Fizz, 5 = Buzz, 7 = Woof. Also, the strings must be printed in that order.)

I wrote a version using partial matching that I'm quite happy with, except for the one darn mutable Boolean in there that makes it feel less than functional to me. Is there a way I can get rid of that mutable Boolean, and still have clean code?

My attempt so far:

module FizzBuzzWoof =

    let isDivisibleBy a b = (a % b) = 0
    let contains a b = a.ToString().Contains(b.ToString())

    let fizzBuzzWoofMatcher n = fun i ->
        if isDivisibleBy i n then Some ()
        elif contains i n then Some ()
        else None

    let (|Fizz|_|) i = fizzBuzzWoofMatcher 3
    let (|Buzz|_|) i = fizzBuzzWoofMatcher 5
    let (|Woof|_|) i = fizzBuzzWoofMatcher 7

open FizzBuzzWoof

let runme () =
    for i = 1 to 100 do
        let mutable matched = false  // Want to eliminate this
        match i with
        | Fizz () -> printf "Fizz"; matched <- true
        | _ -> ()
        match i with
        | Buzz () -> printf "Buzz"; matched <- true
        | _ -> ()
        match i with
        | Woof () -> printf "Woof"; matched <- true
        | _ -> ()
        if matched
            printfn ""
            printfn "%d" i


I did come up with the idea of replacing the final if matched with another match expression as follows:

        match i with
        | Fizz () -> printfn ""
        | Buzz () -> printfn ""
        | Woof () -> printfn ""
        | _ -> printfn "%d" i

And this would work, but it runs the match functions an extra time. In this case this is absolutely fine since they're all quite fast, but what if I have a match function that takes a long time? Is there a way to run each match function just once, yet still avoid the use of mutable?

  • \$\begingroup\$ Not quite a code review as such but my own attempt at solving the problem in a more idiomatic way, maybe you'll find it helpful: gist \$\endgroup\$
    – Alex
    Mar 3, 2016 at 12:15

1 Answer 1


You can avoid using mutable by collecting all matching results and folding them into single bool value.

let matchSmth matchFun msg i = 
match matchFun i with
| Some i -> 
    printf msg
| None -> false

let matchFizz = matchSmth (fizzBuzzWoofMatcher 3) "Fizz"
let matchBuzz = matchSmth (fizzBuzzWoofMatcher 5) "Buzz"
let matchWoof = matchSmth (fizzBuzzWoofMatcher 7) "Woof"

let runme() = 
    for i = 1 to 100 do
        let matched = [matchFizz i; matchBuzz i; matchWoof i] |> List.fold (||) false
        if matched then printfn ""
        else printfn "%d" i
  • \$\begingroup\$ And that's just any? then, right? (I'm not sure if F# has an any? function and whether it is called that, but many languages do.) \$\endgroup\$ Feb 18, 2016 at 13:21
  • \$\begingroup\$ I have just applied or to all results. But Any function may be applied too. In this case Any is equal to [...] |> List.exists id. (id is built-in function with 'x -> 'x signature) \$\endgroup\$
    – Kote
    Feb 18, 2016 at 13:53
  • \$\begingroup\$ I just happened to notice that, because I did a re-implementation of Ruby's collection protocols based on fold instead of foreach as an exercise, and the structure immediately caught my eye, since any? is then literally just "fold with false as initial value and ||] as the folding operation". (Well, actually, any? is lazy in Ruby, so you have to somehow break out early as soon as you find a true value.) \$\endgroup\$ Feb 18, 2016 at 19:23
  • \$\begingroup\$ Thanks; that's what I was looking for and failing to come up with myself. Also, I like the matchSmth function -- I didn't notice that I was duplicating code with those three identical match expressions, but I certainly was. So you've given me two ways to make my code more functional -- thank you. \$\endgroup\$
    – rmunn
    Feb 19, 2016 at 2:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.