layout: post
title: “Railway oriented programming: Carbonated edition”
description: “Three ways to implement FizzBuzz”

categories: []

As a follow up to the Railway Oriented Programming post, I thought I’d apply the same technique to the FizzBuzz problem,
and compare it with other implementations.

A large part of this post was directly stolen from inspired by Dave Fayram’s post on FizzBuzz, with some additional ideas from
raganwald.

FizzBuzz: The imperative version

As a reminder, here are the requirements for the FizzBuzz problem:

  1. Write a program that prints the numbers from 1 to 100.
  2. * For multiples of three print "Fizz" instead of the number.
  3. * For multiples of five print "Buzz".
  4. * For numbers which are multiples of both three and five print "FizzBuzz".

And here is a basic F# solution:

  1. module FizzBuzz_Match =
  2. let fizzBuzz i =
  3. match i with
  4. | _ when i % 15 = 0 ->
  5. printf "FizzBuzz"
  6. | _ when i % 3 = 0 ->
  7. printf "Fizz"
  8. | _ when i % 5 = 0 ->
  9. printf "Buzz"
  10. | _ ->
  11. printf "%i" i
  12. printf "; "
  13. // do the fizzbuzz
  14. [1..100] |> List.iter fizzBuzz

I have defined a function fizzBuzz that, given an integer i, uses match with when clauses to do the various tests, and then prints the appropriate value.

Simple and straightforward, and fine for a quick hack, but there are a number of problems with this implementation.

First, note that we had to have a special case for “fifteen”. We couldn’t just reuse the code from the “three” and “five” cases. And this means that if we want to add another case, such as “seven”,
we also need to add special cases for all the combinations as well (that is “21”, “35” and “105”). And of course, adding more numbers would lead to a combinatorial explosion of cases.

Second, the order of matching is important. If the “fifteen” case had come last in the list of patterns, the code would have run correctly, but not actually met the requirements.
And again, if we need to add new cases, we must always remember to put the largest ones first to ensure correctness. This is just the kind of thing that causes subtle bugs.

Let’s try another implementation, where we reuse the code for the “three” and “five” case, and eliminate the need for a “fifteen” case altogether:

  1. module FizzBuzz_IfPrime =
  2. let fizzBuzz i =
  3. let mutable printed = false
  4. if i % 3 = 0 then
  5. printed <- true
  6. printf "Fizz"
  7. if i % 5 = 0 then
  8. printed <- true
  9. printf "Buzz"
  10. if not printed then
  11. printf "%i" i
  12. printf "; "
  13. // do the fizzbuzz
  14. [1..100] |> List.iter fizzBuzz

In this implementation, the printed value for “15” will be correct, because both the “3” and “5” cases will be used. And also we don’t have to worry about order — as much, anyway.

But — these branches are no longer independent, so we have to track whether any branch has been used at all, so that we can handle the default case. And that has led
to the mutable variable. Mutables are a code smell in F#, so this implementation is not ideal.

However, this version does have the advantage that it can be easily refactored to support multiple factors, not just 3 and 5.

Below is a version that does just this. We pass in a list of “rules” to fizzBuzz. Each rule consists of a factor and a corresponding label to print out.
The fizzBuzz function then just iterates through these rules, processing each in turn.

  1. module FizzBuzz_UsingFactorRules =
  2. let fizzBuzz rules i =
  3. let mutable printed = false
  4. for factor,label in rules do
  5. if i % factor = 0 then
  6. printed <- true
  7. printf "%s" label
  8. if not printed then
  9. printf "%i" i
  10. printf "; "
  11. // do the fizzbuzz
  12. let rules = [ (3,"Fizz"); (5,"Buzz") ]
  13. [1..100] |> List.iter (fizzBuzz rules)

If we want additional numbers to be processed, we just add them to the list of rules, like this:

  1. module FizzBuzz_UsingFactorRules =
  2. // existing code as above
  3. let rules2 = [ (3,"Fizz"); (5,"Buzz"); (7,"Baz") ]
  4. [1..105] |> List.iter (fizzBuzz rules2)

To sum up, we have created a very imperative implementation that would be almost the same in C#. It’s flexible, but the mutable variable is a bit of a code smell. Is there another way?

FizzBuzz: The pipeline version

In this next version, we’ll look at using the “pipeline” model, where data is fed through a series of functions to arrive at a final result.

In this design, I envision a pipeline of functions, one to handle the “three” case, one to handle the “five” case, and so on. And at the end, the appropriate label is spat out, ready to be printed.

Here is some pseudocode to demonstrate the concept:

  1. data |> handleThreeCase |> handleFiveCase |> handleAllOtherCases |> printResult

As an additional requirement, we want the pipeline to have no side effects. This means that the intermediate functions must not print anything.
They must instead pass any generated labels down the pipe to the end, and only at that point print the results.

Designing the pipeline

As a first step, we need to define what data will be fed down the pipe.

Let’s start with the first function, called handleThreeCase in the pseudocode above. What is its input, and what is its output?

Obviously, the input is the integer being processed. But the output could be the string “Fizz” if we’re lucky. Or the original integer if we’re not.

So now let’s think about the input to the second function, handleFiveCase. It needs the integer as well.
But in the case of “15” it also needs the string “Fizz” as well, so it can append “Buzz” to it.

Finally, the handleAllOtherCases function converts the int to a string, but only if “Fizz” or “Buzz” have not been generated yet.

It’s quite obvious then, that the data structure needs to contain both the integer being processed and the “label so far”.

The next question is: how do we know if an upstream function has created a label?
The handleAllOtherCases needs to know this in order to determine whether it needs to do anything.

One way would be to use an empty string (or, horrors, a null string), but let’s be good and use a string option.

So, here’s the final data type that we will be using:

  1. type Data = {i:int; label:string option}

Pipeline version 1

With this data structure, we can define how handleThreeCase and handleFiveCase will work.

  • First, test the input int i to see if it is divisible by the factor.
  • If it is divisible, look at the label — if it is None, then replace it with Some "Fizz" or Some "Buzz".
  • If the label already has a value, then append “Buzz” (or whatever) to it.
  • If the input is not divisible by the factor, just pass on the data unchanged.

Given this design, here’s the implementation. It’s a generic function that I will call carbonate (after raganwald)
because it works with both “Fizz” and “Buzz”:

  1. let carbonate factor label data =
  2. let {i=i; label=labelSoFar} = data
  3. if i % factor = 0 then
  4. // pass on a new data record
  5. let newLabel =
  6. match labelSoFar with
  7. | Some s -> s + label
  8. | None -> label
  9. {data with label=Some newLabel}
  10. else
  11. // pass on the unchanged data
  12. data

The design for the handleAllOtherCases function is slightly different:

  • Look at the label — if it is not None, then a previous function has created a label, so do nothing.
  • But if the label is None, replace it with the string representation of the integer.

Here’s the code — I will call it labelOrDefault:

  1. let labelOrDefault data =
  2. let {i=i; label=labelSoFar} = data
  3. match labelSoFar with
  4. | Some s -> s
  5. | None -> sprintf "%i" i

Now that we have the components, we can assemble the pipeline:

  1. let fizzBuzz i =
  2. {i=i; label=None}
  3. |> carbonate 3 "Fizz"
  4. |> carbonate 5 "Buzz"
  5. |> labelOrDefault // convert to string
  6. |> printf "%s; " // print

Note that we have to create an initial record using {i=i; label=None} for passing into the first function (carbonate 3 "Fizz").

Finally, here is all the code put together:

  1. module FizzBuzz_Pipeline_WithRecord =
  2. type Data = {i:int; label:string option}
  3. let carbonate factor label data =
  4. let {i=i; label=labelSoFar} = data
  5. if i % factor = 0 then
  6. // pass on a new data record
  7. let newLabel =
  8. match labelSoFar with
  9. | Some s -> s + label
  10. | None -> label
  11. {data with label=Some newLabel}
  12. else
  13. // pass on the unchanged data
  14. data
  15. let labelOrDefault data =
  16. let {i=i; label=labelSoFar} = data
  17. match labelSoFar with
  18. | Some s -> s
  19. | None -> sprintf "%i" i
  20. let fizzBuzz i =
  21. {i=i; label=None}
  22. |> carbonate 3 "Fizz"
  23. |> carbonate 5 "Buzz"
  24. |> labelOrDefault // convert to string
  25. |> printf "%s; " // print
  26. [1..100] |> List.iter fizzBuzz

Pipeline version 2

Creating a new record type can be useful as a form of documentation, but in a case like this, it would probably be more idiomatic
to just use a tuple rather than creating a special data structure.

So here’s a modified implementation that uses tuples.

  1. module FizzBuzz_Pipeline_WithTuple =
  2. // type Data = int * string option
  3. let carbonate factor label data =
  4. let (i,labelSoFar) = data
  5. if i % factor = 0 then
  6. // pass on a new data record
  7. let newLabel =
  8. labelSoFar
  9. |> Option.map (fun s -> s + label)
  10. |> defaultArg <| label
  11. (i,Some newLabel)
  12. else
  13. // pass on the unchanged data
  14. data
  15. let labelOrDefault data =
  16. let (i,labelSoFar) = data
  17. labelSoFar
  18. |> defaultArg <| sprintf "%i" i
  19. let fizzBuzz i =
  20. (i,None) // use tuple instead of record
  21. |> carbonate 3 "Fizz"
  22. |> carbonate 5 "Buzz"
  23. |> labelOrDefault // convert to string
  24. |> printf "%s; " // print
  25. [1..100] |> List.iter fizzBuzz

As an exercise, try to find all the code that had to be changed.

Eliminating explicit tests for Some and None

In the tuple code above, I have also replaced the explicit Option matching code match .. Some .. None with some built-in Option functions, map and defaultArg.

Here are the changes in carbonate :

  1. // before
  2. let newLabel =
  3. match labelSoFar with
  4. | Some s -> s + label
  5. | None -> label
  6. // after
  7. let newLabel =
  8. labelSoFar
  9. |> Option.map (fun s -> s + label)
  10. |> defaultArg <| label

and in labelOrDefault:

  1. // before
  2. match labelSoFar with
  3. | Some s -> s
  4. | None -> sprintf "%i" i
  5. // after
  6. labelSoFar
  7. |> defaultArg <| sprintf "%i" i

You might be wondering about the strange looking |> defaultArg <| idiom.

I am using it because the option is the first parameter to defaultArg, not the second, so a normal partial application won’t work. But “bi-directional” piping does work, hence the strange looking code.

Here’s what I mean:

  1. // OK - normal usage
  2. defaultArg myOption defaultValue
  3. // ERROR: piping doesn't work
  4. myOption |> defaultArg defaultValue
  5. // OK - bi-directional piping does work
  6. myOption |> defaultArg <| defaultValue

Pipeline version 3

Our carbonate function is generic for any factor, so we can easily extend the code to support “rules” just as in the earlier imperative version.

But one issue seems to be that we have hard-coded the “3” and “5” cases into the pipeline, like this:

  1. |> carbonate 3 "Fizz"
  2. |> carbonate 5 "Buzz"

How can we dynamically add new functions into the pipeline?

The answer is quite simple. We dynamically create a function for each rule, and then combine all these functions into one using composition.

Here’s a snippet to demonstrate:

  1. let allRules =
  2. rules
  3. |> List.map (fun (factor,label) -> carbonate factor label)
  4. |> List.reduce (>>)

Each rule is mapped into a function. And then the list of functions is combined into one single function using >>.

Putting it all together, we have this final implementation:

  1. module FizzBuzz_Pipeline_WithRules =
  2. let carbonate factor label data =
  3. let (i,labelSoFar) = data
  4. if i % factor = 0 then
  5. // pass on a new data record
  6. let newLabel =
  7. labelSoFar
  8. |> Option.map (fun s -> s + label)
  9. |> defaultArg <| label
  10. (i,Some newLabel)
  11. else
  12. // pass on the unchanged data
  13. data
  14. let labelOrDefault data =
  15. let (i,labelSoFar) = data
  16. labelSoFar
  17. |> defaultArg <| sprintf "%i" i
  18. let fizzBuzz rules i =
  19. // create a single function from all the rules
  20. let allRules =
  21. rules
  22. |> List.map (fun (factor,label) -> carbonate factor label)
  23. |> List.reduce (>>)
  24. (i,None)
  25. |> allRules
  26. |> labelOrDefault // convert to string
  27. |> printf "%s; " // print
  28. // test
  29. let rules = [ (3,"Fizz"); (5,"Buzz"); (7,"Baz") ]
  30. [1..105] |> List.iter (fizzBuzz rules)

Comparing this “pipeline” version with the previous imperative version, the design is much more functional. There are no mutables and there are no side-effects anywhere (except in the final
printf statement).

There is a subtle bug in the use of List.reduce, however. Can you see what it is?** For a discussion of the problem and the fix, see the postscript at the bottom of this page.

** Hint: try an empty list of rules.

FizzBuzz: The railway oriented version

The pipeline version is a perfectly adequate functional implementation of FizzBuzz, but for fun, let’s see if we can use the “two-track” design described
in the railway oriented programming post.

As a quick reminder, in “railway oriented programming” (a.k.a the “Either” monad), we define a union type with two cases: “Success” and “Failure”, each representing a different “track”.
We then connect a set of “two-track” functions together to make the railway.

Most of the functions we actually use are what I called “switch” or “points” functions, with a one track input, but a two-track output, one for the Success case, and one for the Failure case.
These switch functions are converted into two-track functions using a glue function called “bind”.

Here is a module containing definitions of the functions we will need.

  1. module RailwayCombinatorModule =
  2. let (|Success|Failure|) =
  3. function
  4. | Choice1Of2 s -> Success s
  5. | Choice2Of2 f -> Failure f
  6. /// convert a single value into a two-track result
  7. let succeed x = Choice1Of2 x
  8. /// convert a single value into a two-track result
  9. let fail x = Choice2Of2 x
  10. // appy either a success function or failure function
  11. let either successFunc failureFunc twoTrackInput =
  12. match twoTrackInput with
  13. | Success s -> successFunc s
  14. | Failure f -> failureFunc f
  15. // convert a switch function into a two-track function
  16. let bind f =
  17. either f fail

I am using the Choice type here, which is built into the F# core library. But I have created some helpers to make it look like the Success/Failure type: an active pattern and two constructors.

Now, how will we adapt FizzBuzz to this?

Let’s start by doing the obvious thing: defining “carbonation” as success, and an unmatched integer as a failure.

In other words, the Success track contains the labels, and the Failure track contains the ints.

Our carbonate “switch” function will therefore look like this:

  1. let carbonate factor label i =
  2. if i % factor = 0 then
  3. succeed label
  4. else
  5. fail i

This implementation is similar to the one used in the pipeline design discussed above, but it is cleaner because the input is just an int, not a record or a tuple.

Next, we need to connect the components together. The logic will be:

  • if the int is already carbonated, ignore it
  • if the int is not carbonated, connect it to the input of the next switch function

Here is the implementation:

  1. let connect f =
  2. function
  3. | Success x -> succeed x
  4. | Failure i -> f i

Another way of writing this is to use the either function we defined in the library module:

  1. let connect' f =
  2. either succeed f

Make sure you understand that both of these implementations do exactly the same thing!

Next, we can create our “two-track” pipeline, like this:

  1. let fizzBuzz =
  2. carbonate 15 "FizzBuzz" // need the 15-FizzBuzz rule because of short-circuit
  3. >> connect (carbonate 3 "Fizz")
  4. >> connect (carbonate 5 "Buzz")
  5. >> either (printf "%s; ") (printf "%i; ")

This is superficially similar to the “one-track” pipeline, but actually uses a different technique.
The switches are connected together through composition (>>) rather than piping (|>).

As a result, the fizzBuzz function does not have an int parameter — we are defining a function by combining other functions. There is no data anywhere.

A few other things have changed as well:

  • We have had to reintroduce the explicit test for the “15” case. This is because we have only two tracks (success or failure).
    There is no “half-finished track” that allows the “5” case to add to the output of the “3” case.
  • The labelOrDefault function from the previous example has been replaced with either. In the Success case, a string is printed. In the Failure case, an int is printed.

Here is the complete implementation:

  1. module FizzBuzz_RailwayOriented_CarbonationIsSuccess =
  2. open RailwayCombinatorModule
  3. // carbonate a value
  4. let carbonate factor label i =
  5. if i % factor = 0 then
  6. succeed label
  7. else
  8. fail i
  9. let connect f =
  10. function
  11. | Success x -> succeed x
  12. | Failure i -> f i
  13. let connect' f =
  14. either succeed f
  15. let fizzBuzz =
  16. carbonate 15 "FizzBuzz" // need the 15-FizzBuzz rule because of short-circuit
  17. >> connect (carbonate 3 "Fizz")
  18. >> connect (carbonate 5 "Buzz")
  19. >> either (printf "%s; ") (printf "%i; ")
  20. // test
  21. [1..100] |> List.iter fizzBuzz

Carbonation as failure?

We defined carbonation as “success”in the example above — it seems the natural thing to do, surely. But if you recall, in the railway oriented programming model,
“success” means that data should be passed on to the next function, while “failure” means to bypass all the intermediate functions and go straight to the end.

For FizzBuzz, the “bypass all the intermediate functions” track is the track with the carbonated labels, while the “pass on to next function” track is the one with integers.

So we should really reverse the tracks: “Failure” now means carbonation, while “Success” means no carbonation.

By doing this, we also get to reuse the pre-defined bind function, rather than having to write our own connect function.

Here’s the code with the tracks switched around:

  1. module FizzBuzz_RailwayOriented_CarbonationIsFailure =
  2. open RailwayCombinatorModule
  3. // carbonate a value
  4. let carbonate factor label i =
  5. if i % factor = 0 then
  6. fail label
  7. else
  8. succeed i
  9. let fizzBuzz =
  10. carbonate 15 "FizzBuzz"
  11. >> bind (carbonate 3 "Fizz")
  12. >> bind (carbonate 5 "Buzz")
  13. >> either (printf "%i; ") (printf "%s; ")
  14. // test
  15. [1..100] |> List.iter fizzBuzz

What are the two tracks anyway?

The fact that we can swap the tracks so easily implies that that maybe there is a weakness in the design. Are we trying to use a design that doesn’t fit?

Why does one track have to be “Success” and another track “Failure” anyway? It doesn’t seem to make much difference.

So, why don’t we keep the two-track idea, but get rid of the “Success” and “Failure” labels.

Instead, we can call one track “Carbonated” and the other “Uncarbonated”.

To make this work, we can define an active pattern and constructor methods, just as we did for “Success/Failure”.

  1. let (|Uncarbonated|Carbonated|) =
  2. function
  3. | Choice1Of2 u -> Uncarbonated u
  4. | Choice2Of2 c -> Carbonated c
  5. /// convert a single value into a two-track result
  6. let uncarbonated x = Choice1Of2 x
  7. let carbonated x = Choice2Of2 x

If you are doing domain driven design, it is good practice to write code that uses the appropriate Ubiquitous Language
rather than language that is not applicable.

In this case, if FizzBuzz was our domain, then our functions could now use the domain-friendly terminology of carbonated and uncarbonated rather than “success” or “failure”.

  1. let carbonate factor label i =
  2. if i % factor = 0 then
  3. carbonated label
  4. else
  5. uncarbonated i
  6. let connect f =
  7. function
  8. | Uncarbonated i -> f i
  9. | Carbonated x -> carbonated x

Note that, as before, the connect function can be rewritten using either (or we can just use the predefined bind as before):

  1. let connect' f =
  2. either f carbonated

Here’s all the code in one module:

  1. module FizzBuzz_RailwayOriented_UsingCustomChoice =
  2. open RailwayCombinatorModule
  3. let (|Uncarbonated|Carbonated|) =
  4. function
  5. | Choice1Of2 u -> Uncarbonated u
  6. | Choice2Of2 c -> Carbonated c
  7. /// convert a single value into a two-track result
  8. let uncarbonated x = Choice1Of2 x
  9. let carbonated x = Choice2Of2 x
  10. // carbonate a value
  11. let carbonate factor label i =
  12. if i % factor = 0 then
  13. carbonated label
  14. else
  15. uncarbonated i
  16. let connect f =
  17. function
  18. | Uncarbonated i -> f i
  19. | Carbonated x -> carbonated x
  20. let connect' f =
  21. either f carbonated
  22. let fizzBuzz =
  23. carbonate 15 "FizzBuzz"
  24. >> connect (carbonate 3 "Fizz")
  25. >> connect (carbonate 5 "Buzz")
  26. >> either (printf "%i; ") (printf "%s; ")
  27. // test
  28. [1..100] |> List.iter fizzBuzz

Adding rules

There are some problems with the version we have so far:

  • The “15” test is ugly. Can we get rid of it and reuse the “3” and “5” cases?
  • The “3” and “5” cases are hard-coded. Can we make this more dynamic?

As it happens, we can kill two birds with one stone and address both of these issues at once.

Instead of combining all the “switch” functions in series, we can “add” them together in parallel.
In the railway oriented programming post, we used this technique for combining validation functions.
For FizzBuzz we are going to use it for doing all the factors at once.

The trick is to define a “append” or “concat” function for combining two functions. Once we can add two functions this way, we can continue and add as many as we like.

So given that we have two carbonation functions, how do we concat them?

Well, here are the possible cases:

  • If they both have carbonated outputs, we concatenate the labels into a new carbonated label.
  • If one has a carbonated output and the other doesn’t, then we use the carbonated one.
  • If neither has a carbonated output, then we use either uncarbonated one (they will be the same).

Here’s the code:

  1. // concat two carbonation functions
  2. let (<+>) switch1 switch2 x =
  3. match (switch1 x),(switch2 x) with
  4. | Carbonated s1,Carbonated s2 -> carbonated (s1 + s2)
  5. | Uncarbonated f1,Carbonated s2 -> carbonated s2
  6. | Carbonated s1,Uncarbonated f2 -> carbonated s1
  7. | Uncarbonated f1,Uncarbonated f2 -> uncarbonated f1

As an aside, notice that this code is almost like math, with uncarbonated playing the role of “zero”, like this:

  1. something + something = combined somethings
  2. zero + something = something
  3. something + zero = something
  4. zero + zero = zero

This is not a coincidence! We will see this kind of thing pop up over and over in functional code. I’ll be talking about this in a future post.

Anyway, with this “concat” function in place, we can rewrite the main fizzBuzz like this:

  1. let fizzBuzz =
  2. let carbonateAll =
  3. carbonate 3 "Fizz" <+> carbonate 5 "Buzz"
  4. carbonateAll
  5. >> either (printf "%i; ") (printf "%s; ")

The two carbonate functions are added and then passed to either as before.

Here is the complete code:

  1. module FizzBuzz_RailwayOriented_UsingAppend =
  2. open RailwayCombinatorModule
  3. let (|Uncarbonated|Carbonated|) =
  4. function
  5. | Choice1Of2 u -> Uncarbonated u
  6. | Choice2Of2 c -> Carbonated c
  7. /// convert a single value into a two-track result
  8. let uncarbonated x = Choice1Of2 x
  9. let carbonated x = Choice2Of2 x
  10. // concat two carbonation functions
  11. let (<+>) switch1 switch2 x =
  12. match (switch1 x),(switch2 x) with
  13. | Carbonated s1,Carbonated s2 -> carbonated (s1 + s2)
  14. | Uncarbonated f1,Carbonated s2 -> carbonated s2
  15. | Carbonated s1,Uncarbonated f2 -> carbonated s1
  16. | Uncarbonated f1,Uncarbonated f2 -> uncarbonated f1
  17. // carbonate a value
  18. let carbonate factor label i =
  19. if i % factor = 0 then
  20. carbonated label
  21. else
  22. uncarbonated i
  23. let fizzBuzz =
  24. let carbonateAll =
  25. carbonate 3 "Fizz" <+> carbonate 5 "Buzz"
  26. carbonateAll
  27. >> either (printf "%i; ") (printf "%s; ")
  28. // test
  29. [1..100] |> List.iter fizzBuzz

With this addition logic available, we can easily refactor the code to use rules.
Just as with the earlier “pipeline” implementation, we can use reduce to add all the rules together at once.

Here’s the version with rules:

  1. module FizzBuzz_RailwayOriented_UsingAddition =
  2. // code as above
  3. let fizzBuzzPrimes rules =
  4. let carbonateAll =
  5. rules
  6. |> List.map (fun (factor,label) -> carbonate factor label)
  7. |> List.reduce (<+>)
  8. carbonateAll
  9. >> either (printf "%i; ") (printf "%s; ")
  10. // test
  11. let rules = [ (3,"Fizz"); (5,"Buzz"); (7,"Baz") ]
  12. [1..105] |> List.iter (fizzBuzzPrimes rules)

Summary

In this post, we’ve seen three different implementations:

  • An imperative version that used mutable values and mixed side-effects with logic.
  • A “pipeline” version that passed a data structure through a series of functions.
  • A “railway oriented” version that had two separate tracks, and used “addition” to combine functions in parallel.

In my opinion, the imperative version is the worst design. Even though it was easy to hack out quickly, it has a number of problems that make it brittle and error-prone.

Of the two functional versions, I think the “railway oriented” version is cleaner, for this problem at least.

By using the Choice type rather than a tuple or special record, we made the code more elegant thoughout.
You can see the difference if you compare the pipeline version of carbonate with the railway oriented version of carbonate.

In other situations, of course, the railway oriented approach might not work, and the pipeline approach might be better. I hope this post has given some useful insight into both.

If you’re a FizzBuzz fan, check out the Functional Reactive Programming page, which has yet another variant of the problem.

Postscript: Be careful when using List.reduce

Be careful with List.reduce — it will fail with empty lists. So if you have an empty rule set, the code will throw a System.ArgumentException.

In the pipeline case, you can see this by adding the following snippet to the module:

  1. module FizzBuzz_Pipeline_WithRules =
  2. // code as before
  3. // bug
  4. let emptyRules = []
  5. [1..105] |> List.iter (fizzBuzz emptyRules)

The fix is to replace List.reduce with List.fold. List.fold requires an additional parameter: the initial (or “zero”) value.
In this case, we can use the identity function, id, as the initial value.

Here is the fixed version of the code:

  1. let allRules =
  2. rules
  3. |> List.map (fun (factor,label) -> carbonate factor label)
  4. |> List.fold (>>) id

Similarly, in the railway oriented example, we had:

  1. let allRules =
  2. rules
  3. |> List.map (fun (factor,label) -> carbonate factor label)
  4. |> List.reduce (<+>)

which should be corrected to:

  1. let allRules =
  2. rules
  3. |> List.map (fun (factor,label) -> carbonate factor label)
  4. |> List.fold (<+>) zero

where zero is the “default” function to use if the list is empty.

As an exercise, define the zero function for this case. (Hint: we have actually already defined it under another name).