String Calculator Kata in F#

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:

  1. String Calculator Kata (this one)
  2. Another encounter
  3. Fall of exceptions
  4. Happy end

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.

  1. 0 for the input of length 0, which means an empty string
  2. returns the same value converted to integer when passed a single number
  3. 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.