DEV Community

Cover image for Learn functional programming with Advent of Code! ☃️
Ryan Haskell
Ryan Haskell

Posted on

Learn functional programming with Advent of Code! ☃️

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

  1. Let's help some elves!
  2. Print stuff on the screen
  3. Our first Elm program
  4. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

  1. Add up all the calories for each elf
  2. Find the elf with the most calories
  3. 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!"
Enter fullscreen mode Exit fullscreen mode
Hello!
Enter fullscreen mode Exit fullscreen mode

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 a String value into an Html 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 ]
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode

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 ])
Enter fullscreen mode Exit fullscreen mode
[ 1, 2, 3 ]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
[ 1, 2, 3 ]
Enter fullscreen mode Exit fullscreen mode

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
"""
Enter fullscreen mode Exit fullscreen mode
"\n1000\n2000\n3000\n\n4000\n\n5000\n6000\n\n7000\n8000\n9000\n\n10000\n"
Enter fullscreen mode Exit fullscreen mode

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 =
   ...
Enter fullscreen mode Exit fullscreen mode
"1000\n2000\n3000\n\n4000\n\n5000\n6000\n\n7000\n8000\n9000\n\n10000"
Enter fullscreen mode Exit fullscreen mode

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 =
   ...
Enter fullscreen mode Exit fullscreen mode
["1000\n2000\n3000","4000","5000\n6000","7000\n8000\n9000","10000"]
Enter fullscreen mode Exit fullscreen mode

The String.split function took in two arguments:

  1. "\n\n" – the string we want to split things apart with
  2. 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 =
   ...
Enter fullscreen mode Exit fullscreen mode
[["1000","2000","3000"],["4000"],["5000","6000"],["7000","8000","9000"],["10000"]] 
Enter fullscreen mode Exit fullscreen mode

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 =
   ...
Enter fullscreen mode Exit fullscreen mode
[[Just 1000,Just 2000,Just 3000],[Just 4000],[Just 5000,Just 6000],[Just 7000,Just 8000,Just 9000],[Just 10000]]  
Enter fullscreen mode Exit fullscreen mode

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 get Nothing
  • If we call String.toInt "1000", we will get Just 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 =
   ...
Enter fullscreen mode Exit fullscreen mode
[[1000,2000,3000],[4000],[5000,6000],[7000,8000,9000],[10000]]   
Enter fullscreen mode Exit fullscreen mode

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 =
   ...
Enter fullscreen mode Exit fullscreen mode
[6000,4000,11000,24000,10000]
Enter fullscreen mode Exit fullscreen mode

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 =
   ...
Enter fullscreen mode Exit fullscreen mode
Just 24000
Enter fullscreen mode Exit fullscreen mode

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 =
   ...
Enter fullscreen mode Exit fullscreen mode
[6000,4000,11000,24000,10000]
Enter fullscreen mode Exit fullscreen mode

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 =
   ...
Enter fullscreen mode Exit fullscreen mode
[4000,6000,10000,11000,24000]
Enter fullscreen mode Exit fullscreen mode

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 =
   ...
Enter fullscreen mode Exit fullscreen mode
[24000,11000,10000,6000,4000]
Enter fullscreen mode Exit fullscreen mode

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 =
   ...
Enter fullscreen mode Exit fullscreen mode
[24000,11000,10000]
Enter fullscreen mode Exit fullscreen mode

The List.take function takes two arguments:

  1. The amount of items we want to keep (3)
  2. 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 =
   ...
Enter fullscreen mode Exit fullscreen mode
45000
Enter fullscreen mode Exit fullscreen mode

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:

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)