Interesting And Unexpected Benefit Of Pattern Matching

So recently I’ve been working on automating some interactions with a website.  Elixir’s Hound library is a great help, of course.  But I also ran across an interesting and unexpected benefit to pattern matching that greatly simplified some code.

I was trying to dynamically build a URL to pass parameters. The URL would be in the form:

http://www.exampledomain.com?param1=p&param2=q&param3=r

Whenever I see code like this, it’s always a little tricky because you want to add the & at the end of each parameter except the last.  This always used to entail writing a special case in the loop to test if the index of the current element was the maximum index.  But with pattern matching and recursion there’s a much simpler and cleaner solution.  Like this:

defp add_params_to_url(url,[%{:name => name, :value => value}]) when is_binary(url) do     #1
 "#{url}#{name}=#{value}"
end
defp add_params_to_url(url,[%{:name => name, :value => value} | t]) when is_binary(url) do   #2
 url = "#{url}#{name}=#{value}&"
 add_params_to_url(url,t)
end
defp add_params_to_url(url,[]) when is_binary(url), do: url   #3

This is Elixir for those unfamiliar with it.  Basically it’s three function clauses.  The first clause (#1) is hit if only one element is in the list passed into the function. The second clause (#2) is hit if multiple elements are present in the list and the third clause (#3) is hit if the list is empty.

So this is how this works.  When I call add_params_to_url, Elixir will automatically try to match the correct function call dependent on which list I pass.  It will also stop at the first function clause which matches so that other function clauses will not be evaluated.

If I pass a list of URL params which has only one element, clause 1 is matched and the parameter and value are appended and I’m done.  The URL already has the ? on the end, of course.  If I pass a list of URL params with mutliple elements, clause 1 doesn’t match so Elixir jumps down to clause 2 and that matches.  Clause 2 takes the first element from the list tacks it on to the list of parameters with a & at the end and then recursively calls itself with the tail of the list.  If the tail of the list contains more than one parameter, clause  1 will once again fail to match and clause 2 will match again.  If it contains only one parameter, clause 1 is matched.  Because clause 1 doesn’t call itself recursively, clause 3 should never be matched; it’s actually more of a guard case in case someone calls the function with an empty parameter list by mistake.

This, to me, is a small bit of genius.  Much simpler to get exactly the effect I want, that is not having an extraneous character tacked to the end of the string, and it’s nice and simple.

By the way, it occurs to me that I might be able to use a reduce or a fold function to achieve this same effect.  The issue there is that it’s less apparent what it is that I’m trying to do and I also run into the same problem–that is, how do I deal with that last element in the list so I don’t get my separator appended?

5 responses

    • Hi Booker,

      Yes, I kind of thought that is the case. My concern is not so much using something built in to the core libraries as it is readability. To my mind using the pattern matching makes the developer’s intent more obvious. YMMV of course.

      By the way, would you mind sharing the Enum.join code you’re thinking of–maybe via a gist? I wouldn’t mind adding it to my blog post (credited, of course) as a compare/contrast sort of idea.

  1. Here is how you can do it using Enum.join:


    defmodule QueryUtil do
    def add_params_to_url(url, params) do
    url <> query_string(params)
    end
    defp query_string(params) do
    params |> Enum.map(&("#{&1.name}=#{&1.value}")) |> Enum.join("&")
    end
    end
    url = "http://example.com?&quot;
    params = [
    %{name: "test1", value: "value1"},
    %{name: "test2", value: "value2"}
    ]
    result = QueryUtil.add_params_to_url(url, params)
    IO.puts "result: #{inspect result}"
    # ➜ ~ elixir query_util.exs
    # result: "http://example.com?test1=value1&test2=value2&quot;

    view raw

    query_util.exs

    hosted with ❤ by GitHub

  2. Here is how you can do this using Enum.join:


    defmodule QueryUtil do
    def add_params_to_url(url, params) do
    url <> query_string(params)
    end
    defp query_string(params) do
    params |> Enum.map(&("#{&1.name}=#{&1.value}")) |> Enum.join("&")
    end
    end
    url = "http://example.com?&quot;
    params = [
    %{name: "test1", value: "value1"},
    %{name: "test2", value: "value2"}
    ]
    result = QueryUtil.add_params_to_url(url, params)
    IO.puts "result: #{inspect result}"
    # ➜ ~ elixir query_util.exs
    # result: "http://example.com?test1=value1&test2=value2&quot;

    view raw

    query_util.exs

    hosted with ❤ by GitHub

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: