Pattern Matching a Function Body: Maps

You cannot view this unit as you're not logged in yet. Go to Account to login.

28 Comments

  1. sammygadd on November 8, 2020 at 2:03 am

    The first example looks really confusing to me as an Elixir noob. I think it would be a bit easier to understand if the params variables had different names. Overloading them makes things even more confusing. (Or do I completely misunderstand how this works??)

    • Mark Ericksen on November 8, 2020 at 6:08 am

      I’d love to answer your question but I’m not sure I understand it. Are you referring to the example at the “In Pieces and Whole”? Or are you referring to Exercise #1? Which params would you change to improve the clarity?

      • sammygadd on November 9, 2020 at 3:39 am

        I’m referring to the example in “In Pieces and Whole”. The first line declares a params variable. Then the function binds the arguments to another variable also named params.
        At least this is how I understand it. Or are those two variables actually the same?? If not then, to me it would be easier to understand if they had different names to signify that they are different. (A guess simply moving the first line below the function would also help)

        • Mark Ericksen on November 9, 2020 at 4:45 am

          Ah! I see. Thank you for explaining that. The idea with the outer params variable is that it’s like the params you get from Phoenix in a controller from a user POST. They come in with string keys.

          Within the scope of the function do_work, the params argument to the function is different. In this particular case, they would have the same values, but they are different. So yes, I can see that this is confusing. Would it make more sense if the outer params were named phoenix_params or web_params?

          • sammygadd on November 10, 2020 at 12:26 pm

            Yes, for me that makes total sense. I think `web_params` would makes this more clear for noobs like me 🙂
            Thanks!



          • Mark Ericksen on November 10, 2020 at 1:02 pm

            Awesome! Thanks for the feedback! I’ve updated the lesson.



  2. Francisco Quintero on November 25, 2020 at 8:30 am

    Awesome exercises. The last one took me a bit because I wasn’t pattern matching in the function definition but when I realized that, it all went downhill to have passing tests.

  3. romenigld on December 12, 2020 at 5:06 pm

    I loved the explanation and again I was having doubts and needed to understand better was this large of pattern matching and your explanation was very helpful for me.
    Just the second exercise I made a mistake on pattern matching and after review the sample of the NestedBinding.award_bonuses function I realized the test with success and the rest was builded more quickly.
    And it wasn’t needed to put the ‘ = _response’ for match the map.
    And your notes are amazing thank you!

  4. Mark Johnson on February 21, 2021 at 8:29 am

    Awesome lesson!

    • Mark Ericksen on February 21, 2021 at 8:31 am

      I’m glad you liked it!

  5. Chad Henson on February 24, 2021 at 2:42 pm

    I love the format. I notice that I instinctively went to a case statement instead of defining the function multiple times. Are there pros/cons to that approach that I should take into consideration? Specifically, here I am referring to the classify_response(...) function. I did the following

    def classify_response(response) do
      case response do
         %{.... relevant pattern ...} ->
            {ok:, token}
         %{.... another pattern ...} ->
            {error:, invalid:}
      end
    end
    
    • Mark Ericksen on February 25, 2021 at 5:33 pm

      That’s a good question. In a simple example like this, where the code being performed on a match is a simple mapping to a tuple, there isn’t much difference between a case and using separate function bodies.

      In general, I encourage you to reach more often for separate function bodies. Consider what the case statement would look like when more involved logic is performed on each match. It could easily become many lines possibly including nested case statements. A wonderful side-effect of having multiple function bodies is that the logic for what to do in that case is fully contained. This makes the code easier to understand. The functions become very straight forward and single purposed.

      • Chad Henson on February 26, 2021 at 3:26 pm

        Upon further reflection, the suggestion for multiple function bodies seems to be the most “pure” of the functional approach. Each function “does one thing” and your job as a developer is to make it “do that one thing well”. I think you could argue that having multiple pattern matches in one function via a case statement could violate that principle. Especially when you aren’t limiting your pattern matching to only a single “domain” (e.g. customer, order, etc..).

        • Mark Ericksen on February 27, 2021 at 6:44 am

          Well said! I agree that the pattern matches in the function declaration helps keep individual function bodies focused on a single purpose. Functions that are focused on a single thing are much easier to understand, test, and maintain. However, it is also common to use case statements inside functions for simple things like this…

          def place_order(%Customer{} = customer, order_params) do
            case Orders.create(customer, order_params) do
              {:ok, new_order} ->
                # notify customer that order was placed
          
              {:error, reason} ->
                # notify customer of order problem
            end
          end
          

          In this example we need to execute the function Orders.create/2 before we can have data to pattern match on. Also, the code we perform in the case matches will likely be simple. So there are plenty of times we want to use a case too. It becomes a question of complexity and when is the right time to refactor it out into functions.

  6. Uzo Enudi on March 11, 2021 at 6:33 am

    Given that:

    response = %{payload: 123}

    Please, what’s the implied difference between:

    def fun( resp = %{payload: data} ), do: {data, resp}
    and:
    def fun( %{payload: data } = resp ), do: {data, resp}
    

    When fun(response) is called they, I get the same result.

    • Mark Ericksen on March 11, 2021 at 8:52 am

      You are correct. They are functionally equivalent. It is really a personal preference. I think the Credo library even accepts both versions but all of the code must do it consistently the same way.

      The community convention is to define the pattern first and then name it (if desired). The critical part is the pattern, so that’s why I like it first.

      But you are right, they both give the same results. 🙂

      • Uzo Enudi on March 12, 2021 at 11:03 am

        Thank you for such a great response. I’ll stick with the community convention.

  7. Maksym Kosenko on November 4, 2021 at 4:11 pm

    Wow!!! This lesson made me thinking ‘pattern matching’. 💯

    • Mark Ericksen on November 4, 2021 at 8:39 pm

      Awesome! It’ll become your new super power!

  8. Bill Bozarth on November 30, 2021 at 7:20 am

    Learning to program and I am loving your courses Mark!! Thank you for providing them and please make more! Maybe a short one on either using Exercism.io’s problems and/or common problems/challenges concerning app construction and build out? As if you are not doing a lot already. I love what you are doing at Fly.io!!

    Question:
    From my beginner perspective I am confused why there is such a debate about dynamic versus static typing when types appear to be taken care of with patterns like the one you used above for Billing. I have to be missing something, could you explain this to me or provide a link possibly?

    defmodule Billing do
      def apply_charge(%{id: customer_id} = customer, charge) do
        record_charge(customer_id, charge)
        notify_customer(customer, charge)
      end
    end
    
    • Mark Ericksen on November 30, 2021 at 1:48 pm

      Hi Bill! Thanks for the kind words!

      As for static vs dynamic types, it’s a good question and I’m not sure I can do it justice here. I’ll give it a try. You may have already seen this, but in case you haven’t: https://thinkingelixir.com/elixir-in-the-type-system-quadrant/

      Static types are checked at compile time. The code won’t compile if the compiler isn’t satisfied that all the types, variables of those types and the operations on those types are all valid. This gives some greater compile-time guarantees and even enables more powerful refactoring IDE tools.

      Dynamic types are checked at runtime. Like in the pattern matches, the pattern is checked against the data at runtime… during program execution. So it becomes possible to write the following.

      def do_work(customer) do
        IO.puts customer.invalid_field
      end
      

      This will compile… because there is no check or type declaring what customer is here. But it will fail during executing because the field is invalid for the actual data passed in.

      A con for static types is that you spend a lot of time doing work just to satisfy the compiler. A lot of language features (like interfaces) get created just to help provide types while trying to separate them from the data. You end up doing things like “dependency injection” just to work around the constraints of the types. I find it adds a lot of complexity that gets in the way of the expression and intent of the code.

      There are pros and cons to each approach (static vs dynamic). Having worked in both, as you can guess, I prefer dynamic. However, others will prefer static. I hope that at least helps a little in understanding the difference.

      I hope you have fun as you continue on your programming journey!

      • Bill Bozarth on November 30, 2021 at 5:29 pm

        That was more than I hoped for, thanks!
        Oh, this weeks Thinking Elixir talk with Dave Lucia was super interesting for me since I imagine making an app that this stack and architecture might well serve. Thanks, again!

        • Mark Ericksen on December 1, 2021 at 6:21 am

          Awesome! Glad it was helpful!

  9. Kevin Kelleher on January 14, 2022 at 8:11 am

    I’ve got to say, this chapter really excited me. I’ve done a lot of fetching/parsing JSON, and it’s a pain, even with nice libraries or tools like jq.

    The example you gave is a killer. I love it.

  10. Juan Borrás on August 25, 2023 at 3:16 am

    The paragraph that starts with “In this code, we have a string-key map called params similar…” shoud be In this code, we have a string-key map called web_params similar..”

    Notice also that unless web_params contains a map with a key called “email” the do_work/1 function will not be executed and the runtime will throw an exception. So yes, the idiom is powerful but you may want to add some robustness to it all.

  11. Juan Borrás on August 25, 2023 at 3:29 am

    Also notice that:

    %{:name => “foo”} == %{name: “foo”}

    holds, but:

    %{“name” => “foo”} == %{name: “foo”}

    does not.

    • Mark Ericksen on September 12, 2023 at 6:10 am

      That’s correct! In this example the key is an atom for the first map and a string in the second map. They are both valid maps, but different data types make them not equal.

  12. Caleb Josue Ruiz Torres on September 11, 2023 at 5:27 pm

    “… reading it becomes a exercise of mentally parsing code to figure out what it’s doing. ”

    Agree, and I would say this is one of the benefits of functional programming, by declaratively expressing what you want to compute you liberate cognitive load and “mental brute force”, leveraging digital computer’s power.

Leave a Comment

You must be logged in to post a comment.