Learning Elm syntax through TDD

Nicolas Payot
10 min readSep 8, 2017

--

I’ve been wanting to get a good grasp at Elm for quite some time now. Elm is a functional language that compiles to JavaScript. It gained a lot of attraction lately as functional (reactive) programming is increasingly used in front-end development. If you’re a JavaScript developer, I’m quite confident you’ve already heard about one, some or all of these libraries: React, Redux, Cycle.js, RxJS, MobX, Ramda, Immutable.js… All of them embrace functional programming patterns and principles.

Elm is said to be easy to learn and use. Well, this is what we’re going to see by implementing String Calculator Kata with Test Driven Development. Rules are described here: http://osherove.com/tdd-kata-1/. For this exercise, we’ll only focus on the language syntax and patterns.

Getting the environment up and running

Installing Elm

I’ve followed the official guide and chose the npm package to get Elm on my machine. npm install -g elm will give you access to the following command line tools (of course, Node and npm are required to use this method):

  • elm-repl: play with Elm expression
  • elm-reactor: get a project going quickly
  • elm-make: compile Elm code directly
  • elm-package: download packages

It’s no more complicated than that. Only elm-repl and elm-package will be used in the context of this kata exercise.

Testing Elm code

As we’ll explore Elm with TDD, we need to find how to quickly run unit tests to get immediate feedback. Bonus point if they can automatically re-execute themselves as we write and update code (watch mode).

After a quick search, elm-test appears to be a solid candidate. It provides an API (describe, test, Expect, fuzz) and the necessary tooling to run tests locally in a terminal. Install it with the following command: npm install -g elm-test.

Init the project

Initiating a project is really easy. All we need to do is create an empty directory, cd into it and run the following command: elm-test init. Here’s what we will get:

elm-package.json describes the project’s informations and lists the required dependencies. These are downloaded in the elm-stuff directory. Example.elm shows how to create a test suite. First test is not implemented and if we try to run the suite with elm-test command, terminal output message will be quite informative.

Implementing String Calculator Kata

A code kata is an exercise in programming which helps a programmer hone their skills through practice and repetition. The term was probably first coined by Dave Thomas, co-author of the book The Pragmatic Programmer,[1] in a bow to the Japanese concept of kata in the martial arts. — Wikipedia

In Test Driven Development, we move forward with baby steps. First step is to write a failing test. Then, make this test pass with a small amount of production code and then refactor. Finally, repeat the loop until all requirements are met. Shall we get started?

First thought: syntax is weird

In the String Calculator Kata, first step is to create a simple add method that takes 0, 1, or 2 numbers (as a string) and returns their sum. For example, add("") should return 0, add("1") 1 and add("1,2") 3.

Let’s write the first failing test:

elm-test —-watch is the way to run this test suite with watch mode. It means every time we’ll write or update code, a tests run will automatically be triggered. Pretty neat!

First things first. Ok, import statements are quite familiar, but, what is this lack of parentheses and curly brackets? It appears Elm uses whitespace and indentations instead of those (although parentheses are necessary in specific cases).

Here, test is a function that takes a first argument as a string and a second argument as a function that evaluates a single Expectation (actually, test is a function that returns a function. Indeed, functions are curried by default with Elm).

Next, what the heck is this: <|\() ->? Well, <| is used to reduce parentheses usage. For example, leftAligned (monospace (fromString "code")) can also be written that way: leftAligned <| monospace <| fromString "code". Isn’t it better (I guess…)?

Finally, \() -> is used to implement anonymous function. The empty parentheses are what’s called a unit type. A type that can only ever have a single value (which is ()). \_ -> can also be used where _ is a placeholder for any value.

Now, let’s write the code to make this test pass:

Done. Baby step! Let’s go a bit further, shall we? No need to refactor yet, here’s a second failing test where we will try to get the sum of 2 numbers:

Using List and Result

I didn’t get the solution for this test right away. Checking if the string has a , and then split is quite obvious but I had to understand how List works and how to convert String to Int with Elm.

Here, we use the map method to convert our String number to an Int. String has a built-in function (toInt) to do this task, but the thing is that it returns an object of type Result, because this kind of task can fail. And Elm treats errors as data (no runtime errors, looking at you JavaScript!). So, Result is an object that’s either Ok (when task succeeded) or Err (when task failed). Then, all we need is to return the actual value when Ok or a default value (0) when Err. Exactly what Result.withDefault function does.

Finally, we use the List.sum method to get the sum of our Int numbers. Notice the use of |> to reduce parentheses usages. The output of the left part is the input of the right part. I personally find it very convenient and logical after a bit of practice.

Refactoring

What we can do here to refactor is create a function splitWithSeparator whose responsibility is to check if a string has a given separator and then split this string into a list.

In the case where the string of numbers doesn’t contain any separator (it means there’s only one number), we just append the number to a list, so that in both cases, splitWithSeparator will return a List (in Elm, a function has to return the same type in every branch of code).

Handling new lines between numbers

Next step of the kata is to allow add method to handle new line character \n between numbers, so that for example, add("1\n2,3") will return 6.

Let’s add another new failing test for this case:

My first attempt to make this test pass was to split the string with \n as separator and then split again each chunk of string with , as separator, which gives us a list of lists of strings. That’s not very elegant (actually not at all) but it works, thanks to List.concatMap that maps its given function onto a list and flatten the resulting lists.

Regex to the rescue

Although the solution with concatMap works, we can do better. How about using a simple regex to split our string of numbers wherever there’s either \n or , as separator? Let’s refactor the splitWithSeparator method:

Regex.split splits a string into a list using a given Regex.regex separator. It also needs to know how many matches we want to make. Here we need to find all the characters that match the following pattern: \\n|,. So, we use the Regex.HowMany data structure value: Regex.All. Our method is greatly simplified, isn’t it?

Handling custom separator

I think this step is the hardest but hang in there! So now we need to handle custom separator in our add method. A custom separator should be specified at the beginning of the string of numbers, the following way: "//[separator]\n[numbers]". For example: add("//;\n1;2;3") should return 6. Of course, this should be optional and all previous scenarios should still be supported. Let’s repeat the loop and add the failing test:

Here, what comes to mind first is to check if the string contains a custom separator, extract it if present and then repeat the split and convert steps. How does that sound? Ok, get ready, we’re about to get our hands dirty!

What do we have here. 3 new functions: startsWithSeparator, extractSeparator and removeSeparatorPattern. Also, there’s this let ... in thing we’ll explain a bit further.

startsWithSeparator is quite easy, we’ll use Regex again but this time with the contains method:

extractSeparator is a bit touchy. Notice the new regex we’re using: ^//(.+)\\n (see above). Parentheses are here to capture any character(s) that will be between // and \n. This is our custom separator.

Regex.AtMost 1 means we’re looking for 1 match only. Regex.find returns a list of Match objects. Each Match contains a submatches field, which is a list of subpattern(s), the pattern(s) surrounded by parentheses. It can be empty as all regex don’t have subpattern. By using List.concapMap, the lists of submatches are flatten into an unique list. Then, we can extract our custom separator string with List.head, which gives us the first item (in our case, there will always be one item).

The unwrap function is used to extract the current value of a Maybe wrapper. Here, it’s kind of unsafe because if there’s no value (Nothing), the program will crash. However, in our case, it should never happen. We need to apply this util function twice because our custom separator is actually wrapped twice in a Maybe object. Indeed, submatches is a list of strings enclosed in Maybe objects, and List.head returns the head of the list as a Maybe object as well.

Let’s take a step back to let ... in. If you’re familiar with JavaScript (ES6+), you know let can be used to declare variables. Well, it’s the same with Elm except that the declared variables have to be used within a in block. It might seem a bit weird at first but it’s a good way to enclose variables, as they’re not available outside their block.

Finally, removeSeparatorPattern will return a new string, without the custom separator at the beginning. It’s quite straight forward:

Refactoring, again

Now (if you’re still here…), all of our tests should pass but the add method got a little bit complicated. What we could do is create a function to extract the list of string numbers (with or without custom separator) and let the add method only calculate the sum of these numbers.

As you may notice, we remove the second argument of startsWithSeparator function. It’s actually not necessary as we’re always looking for the same customSeparator pattern. extractSeparator is renamed with extractCustomSeparator for more clarity, and its second argument removed as well (we use the hard coded variable customSeparator within the function). Also, we add a splitWithString function as we don’t need regex to split a string of numbers with a given string separator.

As a result, here’s the simplified add method:

Negatives are not allowed

This is the last rule for this kata. Calling add with a negative number should throw the following exception : “Negatives are not allowed” , followed with the negative number (a list if there are multiple negatives). Actually, there are 4 more steps we could add, but that’s plenty enough for this article…

Before writing the corresponding failing test, let’s look at how to handle errors / exceptions with Elm. By searching through the official documentation, we can find an interesting object: Result.

Result is either Ok meaning the computation succeeded, or it is an Err meaning that there was some failure.

By running this unit test, that obviously fails, we get the following error message:

I like how informative it is. Indeed, the add method currently returns an Int object whereas it should be a Result (wrapping a string when Err and an Int when Ok).

First, we need to refactor all of our previous tests so that the assertions look like this: Expect.equal (StringCalc.add "1,2") (Result.Ok 3). Now, all tests should fail, but that’s ok. All we need to do to make them pass is change the return type and value of the add method:

All right, we’re back on track! Only the last one with the negative number should now fail.

Let’s write a function to check if there’s a negative number in the list. It’s quite simple thanks to List API (one line’s enough):

Now, let’s update the add method with this new piece of code:

As you may have noticed, containsNegative expects a list of Int numbers. Thus, we update extractNumbers to return a list of Int (instead of String) numbers .

Test should be green! Ok, it will obviously fail if the negative number is different than -1 but, remember, baby steps!

Refactoring again² (lightly)

By updating extractNumberList we duplicated some code: |> List.map toInt. Indeed, it is necessary in both branches (in case numbers list start with custom separator or not). One simple way to remove this code smell is to extract it into a function that we can use within the add method:

Handling negatives list

Here’s the last failing test we’ll add for this kata:

2 steps are required to make it pass: extract the negative numbers and display them in the error message. Let’s repeat the loop and implement these functions:

Then, add method should look like this:

Finally, all our tests should pass. Isn’t it satisfying?! 😎

All the code is available on my ⭐️ Github ⭐️. I tried to separate each step with a different commit, which should give you a good overview of progress.

Conclusion

I think TDD (if you’re experienced enough with it) is a good way to learn a new language. Although, it might be a good idea to start with a code kata you’re familiar with. I didn’t know String Calculator and I struggled quite a bit at first, having to understand Elm stuffs and figuring out how to fulfil the kata requirements at the same time.

Coming from JavaScript, Elm syntax seems a bit weird and the way to do things is quite disturbing. But that feeling doesn’t last long once you start playing with the core libraries and their API. Documentation is really nice and detailed with lots of examples. Also, error messages are very informative. They’ll show you just what you need to fix your problem.

Please, feel free to add feedback, I’m sure there are better ways (more Elm-ish) to handle this coding exercise. I’d love to hear more about it from Elm developers.

--

--

Nicolas Payot

Senior Frontend Developer at Malt / JS & UI enthusiastic