Hello, there! 👋
My name is Ryan, and I'm making a tiny YouTube series to help folks learn functional programming through Advent of Code. Each video will tackle a daily Advent of Code problem, and walk through how to solve it using Elm, a language for making reliable web apps.
For day one, I wanted to share this written guide to introduce you to the basics of the language. I used to build web applications using HTML, CSS, and JS– and articles like this really helped me wrap my head around learning this new style of coding!
In this article
- Let's help some elves!
- Print stuff on the screen
- Our first Elm program
- Reusing code for Part 2
1. Let's help some elves!
You can visit Advent of Code to see the full description for today's coding puzzle– but we'll summarize it here for you:
- A group of elves need to count calories for their trip
- Each elf writes down how many calories each food item they have in a list like this:
1000
2000
3000
That list above is saying "the first elf has 6000 calories worth of food" (Calculated by adding 1000, 2000, and 3000).
But there are more elves!
Every elf in the group jots down the calories for their food, and uses blank lines to separate their food from the elf in front of them:
1000
2000
3000
4000
5000
6000
7000
8000
9000
10000
In the list shown above, we see foods for all 5 elves in our group. Each
What do we need to do?
The problem asks us to:
- Add up all the calories for each elf
- Find the elf with the most calories
- Return that biggest calorie number
In this example, the answer would be 24,000– because Elf #4 has 7,000 + 8,000 + 9,000 calories worth of food.
But how can we use Elm to get that answer?
2. Printing stuff on the screen
This whole Elm thing is new, let's make sure we feel confident first! Here's a basic Elm program that prints "Hello!" to the screen.
import Html
main =
Html.text "Hello!"
Hello!
Elm compiles to HTML, so this is what we'd see in our web browser if we ran this program.
Elm programs have "functions" and "values":
-
Html.text
is a function that converts aString
value into anHtml
value. -
"Hello!"
is a value that stores the message we want to print on the screen.
Every Elm program has one main
function that runs on startup. When we give main
an Html
value, our program will work!
Can we print more than "Hello!"?
If we try to give Html.text
the wrong input, the Elm compiler will let us know. Let's try to give it a List number
instead of a String
value:
import Html
main =
Html.text [ 1, 2, 3 ]
TYPE MISMATCH ----------------------------------
The 1st argument to `text` is not what I expect:
4| Html.text [ 1, 2, 3 ]
^^^^^^^^^^^
This argument is a list of type:
List number
But `text` needs the 1st argument to be:
String
Here is our first "Elm compiler message". Elm doesn't throw errors on our users– instead it gives compiler errors up front during devleopment.
This error is telling us that Html.text
expected a String
, but we gave it a List number
instead.
Luckily for us, Elm provides a helpful function that lets us print out any value we want: Debug.toString
If we update our code to convert that List number
to a String
, then Html.text
will get the String
value it expects:
import Html
main =
Html.text (Debug.toString [ 1, 2, 3 ])
[ 1, 2, 3 ]
Now our program works, and we can print any kind of value. But these parentheses are going to be hard to work with...
Calling functions step-by-step with pipes
Every Elm function can use a special "pipeline" operator. This is an alternative way to pass an input into a function, from left-to-right.
Here's what the same code looks like, but using the pipeline operator:
import Html
main =
[ 1, 2, 3 ] -- List number
|> Debug.toString -- String
|> Html.text -- Html msg
[ 1, 2, 3 ]
Note: I've added comments like -- List number
to the end of each line, to help you see the shape of each value after each step of the pipeline. These comments aren't required to make our code run, though!
This pipeline-style will let us break our program into small steps, so we can make tiny progress towards getting to our answer.
Hopefully, the next section will show you what I mean!
3. Our first Elm program
Alright, that's a quick intro to the Elm language– let's learn how to actually solve the problem from before:
Which elf has the most food?
We should start by taking that puzzle input as a String
, and printing that onto the screen as HTML. From there, we'll tackle the problem one step at a time!
I've copied the puzzleInput from before, and used """
to create a "multiline" Elm string:
import Html
main =
puzzleInput
|> Debug.toString
|> Html.text
puzzleInput = """
1000
2000
3000
4000
5000
6000
7000
8000
9000
10000
"""
"\n1000\n2000\n3000\n\n4000\n\n5000\n6000\n\n7000\n8000\n9000\n\n10000\n"
This prints out the String, including all the new line characters ("\n"
).
If you want to follow along, I made this Ellie example for you. It's basically CodePen, so you can code Elm apps in the web browser! ( Clicking the "Compile" button in the top-right will run this code! )
Trimming extra white-space
When we print out this puzzle input, we'll see the string has new lines at the beginning and end.
Let's make our first step to use String.trim
to clean those up:
import Html
main =
puzzleInput -- String
|> String.trim -- String
|> Debug.toString
|> Html.text
puzzleInput =
...
"1000\n2000\n3000\n\n4000\n\n5000\n6000\n\n7000\n8000\n9000\n\n10000"
String.trim takes in a String
and returns another String
– but without those extra characters.
I've added back in those Elm comments to make it clear that we still have a String
even after running the trim function.
Splitting food up for each elf
Now that we used String.trim
, we don't have the leading and trailing \n
characters.
What's next? Let's split up each elf's food by using String.split
. If we look at our input, we can see that each Elf has two \n
characters between their food.
That's all we need to split up our string into a list of smaller ones:
import Html
main =
puzzleInput -- String
|> String.trim -- String
|> String.split "\n\n" -- List String
|> Debug.toString
|> Html.text
puzzleInput =
...
["1000\n2000\n3000","4000","5000\n6000","7000\n8000\n9000","10000"]
The String.split function took in two arguments:
-
"\n\n"
– the string we want to split things apart with - The result of
puzzleInput |> String.trim
Note: When we use the |>
operator, the last argument will be the last value in our pipeline. For this example, that means the trimmed puzzle input from the last step!
Making lists of food for each elf
Each elf has their food split up from the other ones– and each elf's food is an item in the list.
But the numbers are still glued together with \n
for each elf...
For each item in the list, let's split those numbers up with another String.split
call:
import Html
main =
puzzleInput -- String
|> String.trim -- String
|> String.split "\n\n" -- List String
|> List.map (String.split "\n") -- List (List String)
|> Debug.toString
|> Html.text
puzzleInput =
...
[["1000","2000","3000"],["4000"],["5000","6000"],["7000","8000","9000"],["10000"]]
Because we want to "split up each string in the list", we need to call List.map
. The List.map function runs on each item, and returns the new item.
Because our input list was a List String
, each item was a String
. When we call (String.split "\n")
on each individual String
item– we end up with a List String
for each item!
Hopefully the HTML output above makes this a bit easier to understand, but now each elf's food is tracked as a List String
.
- Because there are multiple elves, we track the whole thing in a nested
List (List String)
.
We can't add up text!
We are getting really close, but there's a problem... we can't do addition with String
values!
Let's convert these String
values to numbers using Elm's built-in String.toInt
function:
import Html
main =
puzzleInput -- String
|> String.trim -- String
|> String.split "\n\n" -- List String
|> List.map (String.split "\n") -- List (List String)
|> List.map (List.map String.toInt) -- List (List (Maybe Int))
|> Debug.toString
|> Html.text
puzzleInput =
...
[[Just 1000,Just 2000,Just 3000],[Just 4000],[Just 5000,Just 6000],[Just 7000,Just 8000,Just 9000],[Just 10000]]
Uh-oh. This doesn't look right at all!
What the heck is "Just"?
Whoa, whoa, whoa– what's this crazy "Maybe" thing? Why are all our numbers prefixed with that Just
keyword?
In Elm, there's no null
or runtime errors allowed– so any operation that might fail needs to return a Maybe Int
instead of a normal Int
we might expect from something like JavaScript's parseInt
function
This means three things:
- Our Elm program will never crash for bad input!
- If we try to call
String.toInt "banana"
we will getNothing
- If we call
String.toInt "1000"
, we will getJust 1000
So how do we get rid of those weird Just
things?
Luckily, Elm also comes with another helpful function for filtering the Just
/Nothing
stuff out of a list!
Let's change this code to use List.filterMap String.toInt
instead of List.map String.toInt
:
import Html
main =
puzzleInput -- String
|> String.trim -- String
|> String.split "\n\n" -- List String
|> List.map (String.split "\n") -- List (List String)
|> List.map (List.filterMap String.toInt) -- List (List Int)
|> Debug.toString
|> Html.text
puzzleInput =
...
[[1000,2000,3000],[4000],[5000,6000],[7000,8000,9000],[10000]]
Thank goodness– the List.filterMap function took care of those weird Maybe
values!
Adding up each elf's food
Finally, we have some numbers that we can do math with! 😌
Let's use another great built-in function called List.sum
to add up all the numbers for each list:
import Html
main =
puzzleInput -- String
|> String.trim -- String
|> String.split "\n\n" -- List String
|> List.map (String.split "\n") -- List (List String)
|> List.map (List.filterMap String.toInt) -- List (List Int)
|> List.map List.sum -- List Int
|> Debug.toString
|> Html.text
puzzleInput =
...
[6000,4000,11000,24000,10000]
When we combine List.map
and List.sum, we can sum up each item in our list.
That turned our nested List (List Int)
back into a List Int
.
So who's got the most food?
Now each item in the list has the total calorie counts for each elf, we only need one more function.
Let's use that function, List.maximum
, which can find the biggest number in a list:
import Html
main =
puzzleInput -- String
|> String.trim -- String
|> String.split "\n\n" -- List String
|> List.map (String.split "\n") -- List (List String)
|> List.map (List.filterMap String.toInt) -- List (List Int)
|> List.map List.sum -- List Int
|> List.maximum -- Maybe Int
|> Debug.toString
|> Html.text
puzzleInput =
...
Just 24000
You solved it!
That's it! That's the answer we were looking for all along.
Pat yourself on the back, you made it through some scary, new syntax.
As a final step, let's see if our code is flexible enough to solve a slightly the final challenge...
4. Reusing code for Part 2
The next part of the Advent of Code problem asks us to add up the top 3 elf calories, not just the top 1.
Let's rewind a bit by removing that last part with List.maximum
, and having our program return the List Int
we had before:
import Html
main =
puzzleInput -- String
|> String.trim -- String
|> String.split "\n\n" -- List String
|> List.map (String.split "\n") -- List (List String)
|> List.map (List.filterMap String.toInt) -- List (List Int)
|> List.map List.sum -- List Int
|> Debug.toString
|> Html.text
puzzleInput =
...
[6000,4000,11000,24000,10000]
We're back to what we had before– and don't need to deal with all the grouping and int conversion again.
Sorting lists
This is where we left off before calling List.maximum
. It still has all the data we need, but we need to make sure to grab the 3 biggest numbers this time around.
We can group the big numbers together with the built-in List.sort
function:
import Html
main =
puzzleInput -- String
|> String.trim -- String
|> String.split "\n\n" -- List String
|> List.map (String.split "\n") -- List (List String)
|> List.map (List.filterMap String.toInt) -- List (List Int)
|> List.map List.sum -- List Int
|> List.sort -- List Int
|> Debug.toString
|> Html.text
puzzleInput =
...
[4000,6000,10000,11000,24000]
The List.sort function automatically sorts the numbers for us, from smallest to biggest.
Reversing our list
Elm lists have a function called List.take
, but it only grabs the items in the front of the list. When we called List.sort
, all the biggest numbers moved to the end.
Let's use List.reverse
so that the biggest items are in front, instead:
import Html
main =
puzzleInput -- String
|> String.trim -- String
|> String.split "\n\n" -- List String
|> List.map (String.split "\n") -- List (List String)
|> List.map (List.filterMap String.toInt) -- List (List Int)
|> List.map List.sum -- List Int
|> List.sort -- List Int
|> List.reverse -- List Int
|> Debug.toString
|> Html.text
puzzleInput =
...
[24000,11000,10000,6000,4000]
The List.reverse function reversed the order of our list– and things are looking good!
Grabbing the first 3 items
Great! Now we have the three biggest numbers right in front
- The only remaining step is to take the first 3 items with
List.take
:
import Html
main =
puzzleInput -- String
|> String.trim -- String
|> String.split "\n\n" -- List String
|> List.map (String.split "\n") -- List (List String)
|> List.map (List.filterMap String.toInt) -- List (List Int)
|> List.map List.sum -- List Int
|> List.sort -- List Int
|> List.reverse -- List Int
|> List.take 3 -- List Int
|> Debug.toString
|> Html.text
puzzleInput =
...
[24000,11000,10000]
The List.take function takes two arguments:
- The amount of items we want to keep (
3
) - The full list we want to take from
Wait a minute... we know how to do this part!
Now we have the top 3 biggest numbers, so let's add them up!
Earlier, we used an Elm function for adding up all the numbers in a list.
Let's use the List.sum
function one last time to get our answer:
import Html
main =
puzzleInput -- String
|> String.trim -- String
|> String.split "\n\n" -- List String
|> List.map (String.split "\n") -- List (List String)
|> List.map (List.filterMap String.toInt) -- List (List Int)
|> List.map List.sum -- List Int
|> List.sort -- List Int
|> List.reverse -- List Int
|> List.take 3 -- List Int
|> List.sum -- Int
|> Debug.toString
|> Html.text
puzzleInput =
...
45000
Hooray– you're an Elm developer!
Hopefully, you learned something new and feel super smart! Here's the final solution on Ellie
If you want to learn more here are some resources:
- The Elm guide
- The package docs for all the functions we learned today
- My YouTube channel, where I'll be posting more videos
In each video, I'll walk through my thought process step-by-step. If you're like me, having someone talk things through is the easiest way to absorb new information.
Thanks for reading– and I hope you all have a wonderful December! 👋
Top comments (0)