Since @theimowski introduced me to F# years ago, I wanted to try it someday. Finally, I feel like I'm ready for this challenge. And following @swyx suggestion to learn in public, I'll try to explain each step in the form of a blog post.
I will start with one of my favorites, yet nothing complicated, TDD kata - String calculator.
This article is the first part of a series:
And now it's the coding time!
Adding single numbers
Following TDD rules, we will start with a test.
[<Fact>] let ``Passing empty value returns 0`` = let result = Add "" Assert.Equal(0, result)
We see that the surface of our public API is just one method
Add(numbers: string): int
Our first test case verifies the addition of an empty string, which, of course, should be equal to 0.
Making our test pass doesn't require much brainpower:
let Add (numbers: string): int = 0
Moving forward, we can add a few new tests for single values. We also convert our test to Theory in the process.
[<Theory>] [<InlineData("", 0)>] [<InlineData("5", 5)>] [<InlineData("17", 17)>] let ``Passing single number returns the same value`` (input, expected) = let result = Add input Assert.Equal(expected, result)
Of course, our current implementation isn't going to work. We can fix it with an elegant F# feature called pattern matching:
let Add (numbers: string): int = match numbers.Length with | 0 -> 0 | _ -> int numbers
Numbers with a delimiter
Let's try a more complicated case. The next step is to add numbers separated by a comma. First, the test:
[<Fact>] let ``Passing numbers with a seperator adds them`` () = let result = Add "1,2" Assert.Equal(3, result)
This time, we have to adjust the pattern matching we used earlier.
let Add (numbers: string): int = match numbers.Length with | 0 -> 0 | 1 -> int numbers | _ -> numbers.Split([|','|], StringSplitOptions.RemoveEmptyEntries) |> Array.map int |> Seq.sum
Currently, we have three cases based on the length of our input.
- 0 for the input of length 0, which means an empty string
- returns the same value converted to integer when passed a single number
- And now the best part! We split the numbers by comma, map each digit in a string format to integer and finally sum the result. We do it in full F# force using the pipeline operator, which feeds output from the previous function to the next. Doesn't it look great?
Finally, we can add a few more tests to be sure:
[<Theory>] [<InlineData("1,2", 3)>] [<InlineData("5,8", 13)>] [<InlineData("1,2,3,5,8,13", 32)>] let ``Passing numbers with a seperator adds them`` (input, expected) = let result = Add input Assert.Equal(expected, result)
We implemented the first two steps from the Kata description. We can stop here for now. Full code is available on github.
In another episode, I will introduce custom delimiters.