Pattern Matching a Function Body: Tuples

The following exercises use the Pattern Matching project. Here you will focus on making a single test pass at a time.

The tests we are focusing on are in test/tuples_test.exs. Running the following command will execute all the tests in this file. Running all the tests now will show they all fail.

$ mix test test/tuples_test.exs

[...]

Finished in 0.07 seconds
7 tests, 7 failures

Randomized with seed 866762

I’ll spare you the display of the failing tests. Run it for yourself and you can see it! We’re going to tackle them one at a time. For the first one, I’ll walk you through it more step-by-step.

Exercise #1 – Tuples.day_from_date/1

In this exercise, we want to write a function clause that matches on a 3 element tuple and returns one of the values from it. In Erlang, dates are represented as a tuple like this: {2019, 5, 30}. The function you need to write will match on this type of date and return the day number from it.

First, run the test for this one by itself and see it fail and the message. You can do this by putting a : and the line number after the filename. The line number needs to be any line number that’s inside the test. I just chose line 17.

$ mix test test/tuples_test.exs:17

Running this the first time will include a lot of compiler warnings about unused variables. Disregard those for now.

$ mix test test/tuples_test.exs:17
Excluding tags: [:test]
Including tags: [line: "17"]



  1) test day_from_date/1 returns the day number from an erlang date (PatternMatching.TuplesTest)
     test/tuples_test.exs:9
     Assertion with == failed
     code:  assert 5 == Tuples.day_from_date({2018, 9, 5})
     left:  5
     right: nil
     stacktrace:
       test/tuples_test.exs:10: (test)



Finished in 0.07 seconds
8 tests, 1 failure, 7 excluded

Randomized with seed 164689

The error is nicely laid out for us. In the console it is colored for improved readability.

With a failing test ready that demonstrates the correct working behavior, it’s your turn to write the code in the file being tested. In your editor, open lib/pattern_matching/tuples.ex. Find the function we want to write the implementation for.

defmodule PatternMatching.Tuples do
  @moduledoc """
  Fix or complete the code to make the tests pass.
  """

  def day_from_date(erl_date) do

  end

  [...]
end

Modify the argument to make it a Pattern Match and modify the body to the return the desired value. After making a change to the file, re-run the same test (hit the up arrow in the console to repeat the previous command). Does your test pass? If not, correct any mistakes and try again. If you have a passing test or you are totally stuck, check out the solution below.

Exercise #2 – Tuples.has_three_elements?/1

The test we want to focus on is in test/tuples_test.exs. If the line numbers haven’t changed, execute the failing test using:

mix test test/tuples_test.exs:25

Using a text editor, make changes to the function has_three_elements? in the file lib/pattern_matching/tuples.ex.

Make the test pass by using pattern matching in the function declaration. Create a second function clause that handles when it doesn’t match the tuple. Keep re-running the mix test command for that specific test as you test your solutions.

Exercise #3 – Tuples.major_us_holiday/1

The test we want to focus on is in test/tuples_test.exs.

mix test test/tuples_test.exs:40

Using a text editor, make changes to the function major_us_holiday in the file lib/pattern_matching/tuples.ex.

Make the test pass by using pattern matching in the function declaration. Create multiple function clauses to handle the different cases.

For this example, we only care about 3 specific holiday months.

  • If the month is 12, return “Christmas”.
  • If the month is 7, return “4th of July”
  • If the month is 1, return “New Years”
  • For any other month value, return the string “Uh…”

Exercise #4 – Tuples.greet_user/1

The first test to focus on is the “happy path” or “success” version of the function. The tests for this function are broken out into two tests. The success and failure cases.

mix test test/tuples_test.exs:52

The function should be altered to return the a greeting with the user’s name when it is passed in using an {:ok, username} tuple. A specific error text should be returned if given an {:error, reason} tuple.

Once you have the success version passing, write the failure handling version. That test is:

mix test test/tuples_test.exs:56

Exercise #5 – Tuples.add_to_result/1

This one is also broken out into two different test cases. The first is the “success” or “happy path” version. The second test is how it handles data it can’t operate on. Here are the two tests to focus on.

mix test test/tuples_test.exs:65
mix test test/tuples_test.exs:70

This represents a common pattern in Elixir. Receiving an :ok tuple, operating on the data and returning a new :ok tuple with the modified result.

Iterate editing the file and running tests until they both pass.

All Tests Passing!

All the tests in this file should be passing now! Run the tests for the full file (not any specific line number). You are ready to move on.

$ mix test test/tuples_test.exs
........

Finished in 0.07 seconds
8 tests, 0 failures

Randomized with seed 57431

Comments are closed

This is a static version of the site. Comments are not available.

19 Comments

  1. Francisco Quintero on November 24, 2020 at 7:02 pm

    Awesome and thank you!

    These pattern matching exercises have clear my mind and now I feel more confident. In the beginning I thought I wouldn’t complete the exercises without looking at the solution but gave them a try, read my notes, and tests passed!

    The second one was a tricky one, though, but was a nice thing to learn.

  2. Van Damrongsri on November 28, 2020 at 4:09 pm

    Is it a better practice to create multiple function clauses or to create one clause with a case statement?

    • Mark Ericksen on November 28, 2020 at 8:59 pm

      It’s a good question and this may sound like I’m dodging the question, but I’d say it really depends. 🙂

      I prefer a case statement when the code is pretty simple. Like just a simple transformation. When the body of code in a case clause starts to get more involved or complex, I think about doing it differently. Separate function clauses can be easier to test as well.

  3. romenigld on December 12, 2020 at 7:34 am

    Sometimes reading some code I notice I needed to practice and put in my mind exactly these things.
    And here was a good sample to blow my mind.
    I like the exercise with tests, It was very helpful.
    In some books you don’t see this important things you need to understand and it’s very simple now for me.
    Thank’s!

    • Mark Ericksen on December 12, 2020 at 10:23 am

      Awesome! I’m so glad you’re enjoying it!

  4. Mark Johnson on February 20, 2021 at 9:55 am

    Very helpful. Practice makes perfect. Thanks for putting this tutorial together.
    Kinda reminds me of the Ruby Koans project, which was terrific.

  5. Uzo Enudi on March 9, 2021 at 7:47 am

    This is really cool. Got my pattern-matching confidence on a solid level. Much appreciation for this awesome tutorials and tests.

  6. Vitaly Vasiliev on June 5, 2021 at 3:17 am

    Thank you, the best i found so far. Would buy the next course for sure.

    Maybe you can do something with recursion? 🙂

    • Mark Ericksen on June 5, 2021 at 6:50 am

      I’m glad it’s helpful! Yes, the CodeFlow course covers recursion along with a lot more.

  7. Maksym Kosenko on November 1, 2021 at 11:06 am

    The 2nd exercise was difficult for me, I don’t know why. Maybe I need to get used to use pattern matching more often. I have all tests passed with this solution:

    def has_three_elements?(tuple) do
        tuple_size(tuple) == 3
    end
    • Mark Ericksen on November 1, 2021 at 11:27 am

      Yes, that works! However you don’t get any of the benefits of pattern matching. What we’re building up to is letting the function header do the sorting for which function body will be executed. The pattern match replaces your conditional logic… gone are the days of multiple nested IF statements. Things become much clearer, easier to understand and maintain.

  8. Maksym Kosenko on November 1, 2021 at 11:17 am

    And I also interesting where’s exercise #4? why it’s missed.

    • Mark Ericksen on November 1, 2021 at 11:33 am

      Ha! That’s crazy that no one pointed that out before! There is no missing #4… the numbering was off. Fixed. Thanks!

  9. Juan Borrás on August 25, 2023 at 1:35 am

    Is the usage of the match operator in test “return major US holiday for the month” a bug or a feature?

    • Mark Ericksen on September 28, 2023 at 7:41 am

      Can you be more specific? What part of the usage or example do you mean?

      • Juan Borrás on October 4, 2023 at 3:35 am

        File `tuples_test.exs`, line 35, test described with “return major US holiday for the month”. you are using the match operator (`=`) all over the place. Is it (I presume) a bug or a feature?

        • Juan Borrás on October 4, 2023 at 3:36 am

          I am referring to the code in `pattern_matching.zip` with MD5 checksum: f6b0fdd34f7f534997bd2117cde97364

        • Mark Ericksen on October 15, 2023 at 9:48 pm

          Thanks for responding. Since I’m not sure how it would be a bug, I’ll say it’s a feature. The match operator is very powerful in Elixir. In a test, we can use the match operator to assert that it matches the pattern without having to provide an exact match of the value.

          Hope that helps.

          • Juan Borrás on October 23, 2023 at 2:18 am

            I gave it a thought and even though the match operator eventually produces a failed test I think it is misleading in the context of an `assert` evaluation. It could be a valid idiomatic usage of the match operator but I am not as experienced in the language as to avoid a little bit of astonishment while reading it.
            Still, I am thankful to the course author to gave me the opportunity to think about this matter.



Comments are closed on this static version of the site.