One of the most difficult mental shifts when going from imperative to functional programming comes when reading the ‘=’ operator. At least, I’ve found this to be true of me. For the benefit of anyone else with the same problem, I want to present a potentially new and hopefully helpful way of thinking about “assignment” in functional languages.
First off though, a note on scope: This article is about pattern-matching functional languages like Elixir, Erlang and Haskell. It is not applicable (as far as I know) to Lisps.
Here’s a line out of one of my Elixir programs.
{:ok, body} = fetch_feed(account)
Reading this causes immediate problems for the reader who is fresh off an imperative language. Naively translated to Ruby, it looks like:
[:ok, body] = fetch_feed(account)
…which makes no sense, and doesn’t compile, because you can’t assign a value to an array.
The reader reads up on pattern-matching, though, and begins to understand this code a bit better. She realizes it’s kind of like doing regex pattern-matching in Ruby.
match = /(\d{3}) - (.*)/.match("114 - Null Object") match[1] # => "114" match[2] # => "Null Object"
In the Elixir code a successful pattern match results in variables being bound. We can get a little closer to that in our regular-expression example using named captures.
match = /(?<number>\d{3}) - (?<name>.*)/.match("114 - Null Object") match[:number] # => "114" match[:name] # => "Null Object"
This is as far as most explanations of pattern-matching get, in my experience. But it doesn’t tell the whole story.
Let’s look at an alternative syntax for doing regular expression matches in Ruby.
/(?<number>\d{3}) - (?<name>.*)/ =~ "114 - Null Object" number # => "114" name # => "Null Object"
This (little-known) form of regex matching causes local variables to be set as a side effect of the match. (This might not seem quite so strange if you’re aware that Ruby also sets a bunch of Perl-style automatic variables like $& , $1, $2, and so on, as a side effect of a regex match).
This is starting to look a little closer to our original example of a functional “assignment”. But we’re not there yet. Here’s what I think a pattern-matching assignment is really equivalent to in Ruby regex-matching terms:
data = "114 - Null Object" /(?<number>\d{3}) - (?<name>.*)/ =~ data or raise "No match!" number # => "114" name # => "Null Object"
(I’ve extracted the data to match on into a variable to keep the lines shorter)
See the addition of the assertion at the end of the matching operation? That’s the key piece that was missing before.
Because using the ‘=’ operator in a language like Elixir doesn’t just pattern-match. It asserts that the pattern match will be successful. If it doesn’t succeed, the code will fail at that point. This is the realization that I came to about pattern-matching languages: the ‘=’ operator isn’t assignment. It’s a pattern-matching assertion which may, as a side effect, bind variables.
Sometimes the pattern is very simple. A line like this will always succeed:
result = fetch_feed(account)
That’s because Elixir has a rule that says, in effect, “a pattern consisting of a bare variable name will match any value”. Oh, and coincidentally it will also assign that value to the variable name.
In effect, assigning to a bare variable is like matching on a universal regex:
data = "114 - Null Object" /(?<result>.*)/ =~ data or raise "No match!" result # => "114 - Null Object"
(Note that other languages handle bare variable names a little differently, and add the proviso that the variable must previously be un-assigned in order to match anything.)
So when you see code like this:
{:ok, body} = fetch_feed(account)
You can read it like this: “Try and match the result of fetch_feed(account) against a pattern consisting of a two-element tuple, where the first element is the atom :ok, and the second element can be anything. If the match succeeds, also assign the second element to the variable name body. If it fails, raise an exception immediately.”
I hope this helps someone.
The best way to think of it is as ‘=’ works like in high school algebra, in which the statement x = x + 1 would of course make no sense.
That statement is valid in Elixir.
So I gather, I will admit I have not dived into Elixir yet. I am pretty happy with stock Erlang, but I am sure it will happen sooner or later.
In some ways when you come to erlang or haskell (esp haskell) the hardest part can be to shed some of the assumptions from other languages.
Hello, Zach,
I understand what you’re saying, but in this case, Elixir will try to rebind the alias (I hate to call them variables, because they really aren’t) on the left to whatever value is on the right. In Elixir, you can use the ^ character to force Elixir to do an assertion using the original value of x instead of rebinding to make the assertion return true. So, your high-school-algebra example would be accurate if written like this:
^x = x + 1
Well, by “accurate” I mean it would be false.
I grasped pattern matching during Dan Grossman’s “Programming Languages” course @ Coursera. Even when learning scala I didn’t really grasp it, until Dan explained it using ML. I got hook on pattern matching since then. It can even pattern match on type, which can be handy at times. I sort of disliked every programming language after that. I’ve been trying elixir and erlang, but there seems to be an obsession with ‘primitives’. Can make reading and writing code an unpleasant experience. I’ve been weened from writing functions with more than 3 arguments, and in erlang/elixir they seem to have functions with an unwieldy amount of arguments. Or maybe I just haven’t grasped an elegant way of doing it yet. At least in ML you can create a type to encapsulate a data structure.
Thanks for the helpful post. I’ve seen blogs say it isn’t assignment, then their examples look like an assignment! They always seemed to be contradicting themselves. Saying it is matching, then assignment, finally corresponds to reality!