“Whenever trying to make complicated systems and understand them, it’s crucial to divide the things up into as many pieces as I can, each of which I understand separately. I would like to understand the way of adding things up independently of what it is I’m adding up.”  Gerald Sussman
Implementing a 4bit RippleCarry Adder
Let’s work through an example to understand how functions that solve simple problems can be combined to solve a complex problem. The problem we want to solve is simulating the behavior of a 4bit ripplecarry adder using an Elm program. “4bit ripplecarry adder” is a bit of a mouthful, so let’s break down what that means word by word.

4bit: “bit” is short for binary digit. So 4bit means a four digit binary number, like
1001
. 
RippleCarry: Remember how in the early days of learning addition, you were taught that when you add
5
+5
you carry the1
left one column, resulting in10
? Then once you were comfortable with that concept, you moved on to math problems with two carries — like adding95
+5
. First, you carry the one that is the result of5
+5
left one column, but then when you add1
to9
in that column you need to carry another1
to the another left column, resulting in100
. That’s basically what a “ripplecarry” is — each number that is carried out of one column is then carried in to the next column. 
Adder: A digital circuit that adds two binary numbers.
First, let’s understand how the binary system works before diving deeper into the ripplecarry adder.
Binary System
The decimal numeral system is the mathematical system most people are familiar with. In the decimal system, the integers 0
through 9
are combined in different ways to represent all numbers. It uses base10 notation, which means each digit in a number is ten times larger than the digit to its right. The figure below breaks down the decimal number 4608
so you can see for yourself – the digits start from 10^{0} on the right, then the next digit to the left is ten times larger at 10^{1}, then the digit to the left of that is ten times larger at 10^{2}, and so on.
The binary system, however, has only two integers — 0
and 1
. It uses base2 notation, which means each digit in a binary number is two times larger than the digit to its right. Here’s an example of the binary number 1101
:
If you are wondering what decimal number the binary 1101
is equivalent to, it’s easy to find out. Just evaluate the bottom row of the above figure as one mathematical expression, and you will see that you get a final result of 13.
13
is the decimal equivalent of 1101
in binary.
Binary Addition
Adding binary numbers is much like adding everyday decimal numbers, except that it carries on a value of 2
instead of 10
. For example, in decimal, if we add 6
+ 3
we get 9
. But if we add 6
+ 4
, we’ve run out of single integers to represent what comes next! That’s when the digit on the far right resets to zero, and we now have a one to carry into the next digit spot on the left.
In binary, if we add 1
+ 1
we are in the same pickle as the decimal 6
+ 4
example above: we have exhausted the integers available to us. When you run out of integers, it’s time to reset the digit on the right to zero and carry the one into the next digit spot on the left.
By this logic, it’s easy to keep adding binary numbers together. Let’s find out what the decimal number 4
is equivalent to in binary. Remember, every time we run out of numbers, that means it’s time to reset to zero and carry the one:
Now we can see, 4
in decimal system representation is 100
in binary.
Adding Numbers with a 4bit RippleCarry Adder
A 4bit ripplecarry adder can add binary numbers up to four digits. The figure below shows an example, adding the binary numbers 1110
+ 1011
, with the digits to carry shown in purple.
A ripplecarry adder adds two digits at a time starting from the right. If there’s a carry, it propagates that to the next addition. Once it’s done adding all four digits, the output is presented to us like this:
A_{0}, A_{1}, A_{2}, and A_{3} represent the individual digits of the first number. B_{0}, B_{1}, B_{2}, and B_{3} represent the individual digits of the second number. S_{0}, S_{1}, S_{2}, and S_{3} represent the individual digits of the sum. C_{out} represents the most significant digit of the sum. The most significant digit is the digit that has the greatest value. In the case of a binary number, that’s the digit all the way to the left. Here is a circuit diagram for a 4bit ripplecarry adder.
C_{in} is the carryin bit from the previous addition. C_{out} is the carryout bit that will be passed to the next addition.
The circuit above is what we want to simulate in Elm. Where should we start? Well, the first thing we need to do is look for ways to break the problem at hand into smaller problems. If we look closer, we can see that a ripplecarry adder uses the same adder repeatedly but with different inputs and outputs. This adder acts as a building block for a ripplecarry adder and is called 1bit full adder. Let’s figure out how we can implement a 1bit full adder before we implement a ripplecarry adder.
1bit Full Adder
A 1bit full adder adds only two binary digits, but it’s capable of accepting a carryin value as well. It takes three inputs and produces two outputs as shown in the figure below:
A_{n} and B_{n} represent the binary digits that need adding. C_{n} represents the carryin digit, and C_{n+1} represents the carryout digit. S_{n} represents the least significant digit of the sum. The least significant digit is the digit that has the lowest value. In a binary number, that’s the digit all the way to the right. Here’s a truth table that shows all the possible inputs and outputs produced by a full adder:
Can we break a 1bit full adder further into something even smaller? As it turns out, we can as shown in the figure below.
A full adder can be implemented using two half adders and an OR gate. We’ll find out what an OR gate is soon, but first let’s understand how a half adder works.
Half Adder
A half adder also adds two binary digits, but unlike a full adder it doesn’t accept a carryin value.
Here’s a truth table that shows all the possible inputs and outputs for a half adder:
We can break its implementation into even smaller components: AND gate, OR gate, and inverter.
AND Gate
An AND gate takes two input signals and returns an output that is the logical and of the inputs. It’s equivalent to the &&
operator in Elm.
OR Gate
An OR gate takes two input signals and returns an output that is the logical or of the inputs. It’s equivalent to the 
operator in Elm.
Inverter
An inverter takes one input signal and inverts it. If the input is 0
it returns 1
as the output. If the input is 1
, it returns 0
as the output. It’s equivalent to the not
function in Elm.
Now that we have broken down our original problem (4bit ripplecarry) into its essential building blocks (AND gate, OR gate, and inverter), we can start building a solution by first implementing the building blocks.
Implementing an AND Gate
Let’s create a separate file that will contain all the code we will be writing as we implement different parts of a 4bit ripplecarry adder. In the elmexamples
directory, create a new file called RippleCarryAdder.elm
.
Add the following code to the RippleCarryAdder.elm
file.
We declared a new module called RippleCarryAdder
and imported another module called Bitwise, which includes functions that manipulate individual bits. As it so happens, the Bitwise.and
function does exactly what our AND gate is supposed to do which is to compute a logical and of two input signals.
You may wonder why we didn’t just use the &&
operator to implement the andGate
function. That’s because it only works for boolean values. Since we are trying to simulate a digital circuit, we want to work with 0
s and 1
s instead of True
and False
although conceptually they are equivalent.
Generally, it doesn’t make sense to create a new function that just wraps an existing function, but the name andGate
is bit more revealing in the context of a digital circuit than just and
. Let’s load it up in the repl and verify that it works as expected. Run the elmrepl
command from the beginningelm
directory to launch the repl, and expose the andGate
function when importing the RippleCarryAdder
module like this:
Unlike the Playground
module, we didn’t write a main
function in the RippleCarryAdder
module. That’s because we will be executing all code in that module from the repl. We need a main
function only if we want to run our Elm code in the browser.
Although there’s only one function in the RippleCarryAdder
module right now, we used (..)
to expose everything in it. It’ll make it easier for us to test things from that module as we add more functions to it. Since our function names will continue to be very descriptive, there’s a very little chance of them colliding with other names that might have already been imported.
Implementing an OR Gate
The Bitwise
module also includes a function for computing a logical or of two signals. Let’s use that to implement an OR gate. Add the following function definition to the bottom of RippleCarryAdder.elm
.
Let’s load it up in the repl and verify that it works as expected.
We don’t need to reimport the RippleCarryAdder
module whenever we make any changes to it. The repl automatically reloads a module when it detects a change. Isn’t that nice?
Implementing an Inverter
There is no suitable function in the Bitwise
module that does what an inverter is supposed to do. So let’s roll our own. Add the following function definition to the bottom of RippleCarryAdder.elm
.
The inverter
function flips the input signal using a case
expression. If the input signal is 0
it returns 1
and vice versa. If the input signal is anything other than 0
or 1
, it returns 1
. We used negative 1
to indicate that the input signal doesn’t represent a valid binary number, although any number other than 0
and 1
would work.
If we don’t add the _ > 1
catchall pattern, Elm will complain because without it we wouldn’t be accounting for all possible integer values the parameter a
can hold. This is not an elegant way to implement an inverter, but it gets the job done. Once we get introduced to types later in this chapter, we can refine this implementation.
Let’s verify that inverter
works as expected in the repl.
Now that we have implemented the basic building blocks, we can start implementing the more complex parts.
Implementing a Half Adder
Add the following function definition to the bottom of RippleCarryAdder.elm
.
The halfAdder
function mimics the half adder circuit we saw earlier. It uses all three logic gates we implemented before to compute a sum and a carry which are then returned as part of a record. The constants d
and e
hold onto the intermediate results that get passed to an AND gate to compute the final sum.
In the Using Tuples section, we learned that when we want to return multiple values from a function, we should consider using a tuple. But records are also useful for returning multiple values. If we use a record in this instance, when we look at the output of halfAdder
, it’ll be easier for us to recognize which digit is a sum and which digit is a carry.
In the record above we added the comma in front of sum = sumDigit
instead of adding it at the end of the previous line. In most languages, it’s best practice to add commas at the end when we create a collection, but the Elm style guide recommends adding them in the front. That’s because it’s easier to spot a missing comma when they’re all in the front. For example, here’s a bigger record with commas in the end:
It’s hard to notice that the third line is missing a comma. But if we move them all to the front it’s much easier to spot a missing comma.
The Elm compiler will easily catch errors like this, but by putting the commas in the front we save ourselves an extra trip to the compiler land. Let’s verify that the halfAdder
function works as expected.
Implementing a 1bit Full Adder
Let’s implement a full adder next by using two half adders and an OR gate. Add the following function definition to the bottom of RippleCarryAdder.elm
.
The fullAdder
function also mimics the full adder circuit we saw earlier. A full adder is capable of taking a carryin value as one of the inputs. So we give it a third parameter that represents a carryin value in addition to the two input signals: a
and b
. Let’s verify that the logic in fullAdder
works as expected.
Implementing a 4bit RippleCarry Adder
Finally, we’re ready to implement a 4bit ripplecarry adder. Add the following function definition to the bottom of RippleCarryAdder.elm
.
rippleCarryAdder
repeatedly uses the fullAdder
function to compute the final sum and a carryout. Let’s verify that it works as expected.
To summarize, we took a complex problem of building a 4bit ripplecarry adder and broke it down to its essential building blocks (AND gate, OR gate, and inverter). We used those building blocks to implement a half adder. We then used a half adder and an OR gate to implement a full adder. Finally, we combined four full adders to implement a 4bit ripplecarry adder.
We used small reliable functions to build bigger and equally reliable functions. This pattern of solving a complex problem by combining small functions is quite common in functional programming languages such as Elm. The main reason this pattern works is because all functions in Elm are pure.
Improving Inputs and Outputs
The rippleCarryAdder
function above accepts the input signals as tuples and returns the output as a record. This makes reading the inputs and outputs a bit difficult. It would be much more readable if they looked something like this:
Improving Inputs
Unlike most other languages, Elm doesn’t provide a way to represent binary numbers yet. For example, in JavaScript we can represent them by adding 0b
as the prefix:
Therefore, we will have to do some extra work to be able to pass binary numbers as inputs to the rippleCarryAdder
function. The first thing we need to do is extract digits from a binary number so that we can pass them individually to different full adders. Modify the rippleCarryAdder
function as shown below so that it now accepts numbers instead of tuples:
We used a nonexistent function called extractDigits
to extract digits from the input numbers. Let’s implement that function next. Add the following function definition to the bottom of RippleCarryAdder.elm
.
extractDigits
accepts a number representing binary digits and takes it through several transformations until all digits have been extracted. Let’s understand how each transformation works.
You don’t have to try the code listed in each step below. It’s there to show how extractDigits
works. Some of it won’t even compile because we haven’t implemented all the necessary functions used inside extractDigits
.
Step 1: Convert the input number into a string.
Step 2: Split the string into a list.
Step 3: Convert each string element inside the list back to a number.
It’s a roundabout way of converting a number into a list of digits because Elm doesn’t provide a function that does that.
Step 4: Convert the list into an array so that we can access each digit by specifying an index.
Step 5: Convert the array into a tuple.
It’s quite convenient to use a tuple to define multiple constants in one go like this:
Hopefully you now understand how the extractDigits
function works. Next, let’s implement the two nonexistent functions (stringToInt
and arrayToTuple
) we used inside extractDigits
. Add the following function definitions to the bottom of RippleCarryAdder.elm
.
The Array
module isn’t imported automatically by Elm. So we need to do that ourselves. Import it right below the line that imports the Bitwise
module in RippleCarryAdder.elm
.
As its name suggests the stringToInt
function takes a string and converts it into an integer. Because the String.toInt
function returns a value of type Result
instead of an integer, we need to do a little bit of extra work to convert a string to an integer.
We will cover the Result
type in detail later in this chapter, but for now think of it as a container that either contains the successful outcome of an operation or an error. It’s defined in Elm like this:
Don’t enter the Result
type definition shown below into the repl.
So if the operation succeeded as it did in String.toInt "1"
, it contains Ok
followed by the output value: Ok 1
. Otherwise, it contains Err
followed by the error message. Here’s an example:
As you can see, String.toInt
cannot guarantee that it will always be able to convert a string into a number. That’s why it returns a Result
instead of an integer. But what about this code in the stringToInt
function:
The Result.withDefault
is a convenient function that returns the value if the result is OK
, but if the result is an Err
it returns a default value, 1
in our case to indicate an invalid binary number.
Next, we will go through the code in arrayToTuple
function. As its name suggests, the arrayToTuple
function takes an array, reads values at each index and puts them in a tuple. As we learned in
the Array section, we can use the Array.get
function to read a value from an array at a specific index.
Like String.toInt
, Array.get
also doesn’t return the value we’re looking for directly. It returns a Maybe
instead. Maybe
works very similarly to Result
. It’s defined in Elm like this:
So if the value we’re looking for exists, it contains Just
followed by the value. Otherwise, it contains Nothing
. Like String.toInt
, Array.get
cannot guarantee that there will be a value at a given index.
Maybe.withDefault
is also a convenient function that returns the value if it exists, but if the value isn’t there it returns a default value instead.
Now if we reload the rippleCarryAdder
and run it again, we should be able to pass input signals as numbers instead of tuples.
Ah, much better! Not sure if you noticed, but rippleCarryAdder
has a bug: if an input binary number has zeros in the front, the output is incorrect.
When we add 1001
and 0011
, the output should be 1100
but we’re getting 11000
. The problem is most likely in the extractDigits
function. Let’s find out which step is causing this issue.
The root cause of the issue is toString
. It gets rid of leading zeros when converting a number to a string. One way to fix this issue is by padding the output of toString
with enough zeros before passing it to the String.split
function like this:
But this whole approach of converting a number to a string which gets padded with zeros and then gets converted back to numbers feels a bit clumsy. Why don’t we write a function that directly converts a number into a list of digits without involving any string operation? Add the following function definition to the bottom of RippleCarryAdder.elm
.
The digits
function takes a number and returns a list of digits.
When we set out to implement a 4bit ripplecarry adder, we broke the problem at hand into smaller subproblems, solved them individually and combined the results. The digits
function follows a similar approach, but instead of using other functions to solve the subproblems it solves them by itself. To understand how it does that, let’s break the problem of extracting digits from 1100
into subproblems.
We start with the number 1100
which is our original problem. We take the last digit from it and reduce the problem to 110
. We then take the last digit from 110
and further reduce the problem to 11
. We continue to do this until there are no digits left in which case we simply return an empty list. Then we combine the solution to each subproblem one at a time moving from left to right.
Recursion
This process of solving a problem by first solving the smaller versions of the same problem is called recursion. The problem eventually gets reduced to something so small that we can just solve it directly. This is called a base case. In the example above, when there’s no digit left in the number we know that we’ve arrived at the base case. We solve it by simply returning an empty list.
Like most programming languages, Elm supports recursion by allowing a function to apply itself as we have done in the definition of digits
. When using recursion, we need to keep the following three things in mind.
How do we reduce the problem  If a problem can’t be reduced to smaller versions of itself then recursion probably isn’t the best tool to solve that problem. Therefore, we need to know how exactly are we going to reduce the problem. The digits
function does this by dividing the number
by 10
.
The //
operator performs integer division, which truncates everything after the decimal point. We didn’t use recursion to build a 4bit ripplecarry adder because we couldn’t reduce the problem into smaller versions of itself. Therefore, we had to assemble a bunch of other functions to solve the original problem instead of applying itself repeatedly.
What is the base case  A properly designed recursive function must simplify the problem with each invocation so that it will eventually reach the base case. Once the execution hits the base case, then it pops back out and starts gathering the results from each invocation. If we don’t provide a base case, a recursive function will create an infinite loop. Here’s the base case from the digits
function:
After the third invocation of the digits
function, the number 1100
gets reduced to 1
. When integer division is performed on that number by 10
, the number
becomes 0
. At that point the condition for the base case is satisfied and an empty list is returned. If we didn’t have this condition, the digits
function would run forever.
How do we combine the result of each sub problem  Once the base case is reached, we need to provide a mechanism for combining the result from each invocation. The digits
function does this by using the ++
operator.
To see how recursion works behind the scenes, let’s walk through each step in the digits
function’s execution.
Breaking down a recursive function’s invocations like this is fine when we are trying to understand how recursion works in the beginning, but once we have developed an intuition for recursion we should avoid thinking explicitly about the sequence of invocations. We should instead focus on the three things we discussed above.
Hopefully you now understand how recursion works. If not, don’t worry. It’s a tricky concept to master. You just need more practice writing recursive functions and reasoning through them. There are many resources out there for diving deeper into recursion. I recommend checking out Khan Academy’s minicourse that not only explains recursion in simple terms, but also provides exercises for you to practice.
Improving the digits
Function’s Performance
In Elm, it’s more efficient from a performance standpoint to build a list by adding an element to the front using the cons (::
) operator than by appending values at the end using the ++
operator. Let’s say we have a list like this: [ 2, 3, 4, 5 ]
. And we want to add 1
to the front of that list.
The cons operator doesn’t traverse the entire list before adding the given number to the front. Whereas ++
does. Therefore this code:
Essentially turns into this behind the scenes:
We used ++
to append lists with single elements in the digits
function. Now that we know the cons operator is more performant, let’s change the digits
function to use that. In the above examples, the calculations being done are so small we won’t see much of a speed difference between the two operators. It’s when we are passing a very big number through the digits
function that the speed differences really start to add up. Change the digits
function in RippleCarryAdder.elm
to use the cons operator like this:
With your new found understanding of recursion, you should be able to figure out how the new implementation works. Change the extractDigits
function in RippleCarryAdder.elm
to use the new digits
function:
The extractDigits
function now looks much more succinct with the digits
function. Here’s how it looked before:
The new version still doesn’t solve the problem of adding a number with leading zeros, but it’s quite easy to pad zeros to the front of a list. Add the following function definition to the bottom of RippleCarryAdder.elm
.
The padZeros
function pads a list with zeros until it has a given length represented by the parameter named total
. Let’s apply padZeros
in extractDigits
.
Now when we add a binary number with leading zeros, we get a correct result.
Improving Output
Let’s turn our attention to the output of the rippleCarryAdder
function. Currently it looks like this:
We want it to look like this instead:
Ideally, we want it to look like this: 01100
, but Elm truncates the leading zeros from both the repl and code file.
It’s not that difficult to show the output as a number. All we have to do is assemble the sum and carryout digits in a list and turn that list into a number. Modify the rippleCarryAdder
function to look like this:
The only thing that has changed is the part inside the in
area. Let’s go through the new code step by step.
You don’t have to try the code listed in each step below. It’s there to show how rippleCarryAdder
produces a binary number as an output. Some of it won’t even compile because we haven’t implemented all the necessary functions used inside rippleCarryAdder
.
Step 1: Put the records produced by each full adder into a list.
Step 2: Map the list of records into a list of sum digits.
As mentioned in the Record section, the map
function can be used to transform a list of records into a new list that contains values from a specific property. We used a special function called .sum
to extract the sum digits from each record.
Step 3: Add the carryout digit to the front of the list.
Step 4: Convert the list of digits into a number.
numberFromDigits
is a function we haven’t implemented yet. Let’s do that next. Add the following function definition to the bottom of RippleCarryAdder.elm
.
The numberFromDigits
function uses foldl
to reduce a list of digits into a single number. If you don’t remember how foldl
works, you might want to refresh your memory. The figure below shows how numberFromDigits
combines the digits to create a number by repeatedly applying the anonymous function given to foldl
.
Now both inputs and outputs generated by the rippleCarryAdder
function look much nicer.
In the next section we will learn how to verify that the rippleCarryAdder
function behaves as expected using tests.