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:
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?