Comparing complex terms in Elixir

An interesting feature of Erlang, and thus Elixir, is that any 2 data types can be compared to each other using ==, !=, ===, !==, <=, >=, < and >. What does that really mean though? When working with simple data types, it's easy to understand operators: 1 > 2 and "hello" < "world". What happens when we start comparing different types of data:

1 > "hello"  
# => false

Why is it false? Simple. Erlang says so. According to the Erlang docs:

The arguments can be of different data types. The following order is defined:

number < atom < reference < fun < port < pid < tuple < map < [...]* < list < bit string

So numbers are the smallest possible data types when comparing data. This causes some awkward scenarios such as nil > 2 #=> true.

*I took nil out of the list, because Elixir's nil is actually an atom.

Complex data types

So what about complex data types? Let's dive in, but I'm running out of coffee, so let's hurry.

Lists

Lists are easy. According to the Erlang docs:

Lists are compared element by element.

What that means is that given 2 lists: l1 = [1, 2, 3] and l2 = [3, 2, 1], Elixir will compare them element by element. 1 is less than 3, so l1 is less than l2.

Tuples

Tuples are a little more complex, but still pretty easy:

Tuples are ordered by size, two tuples with the same size are compared element by element.

So given the 2 tuples: t1 = {"foo", "bar", "baz"} and t2 = {"hello", "world"}, t1 has a greater size and therefore, t1 is greater than t2. If the tuples are the same size, the same rules as lists are followed.

Maps

Okay, maps get a little more difficult, but my coffee cup is empty, so let's do this. According to the Erlang docs:

Maps are ordered by size, two maps with the same size are compared by keys in ascending term order and then by values in key order.

Let's look at 2 maps: m1 = %{a: 1, b: 2, c: 3} and m2 = %{c: 1, b: 2, a: 3}. The rules for maps say that first, both maps are compared by their keys, so let's look at the keys:

m1 |> Map.keys # => [:a, :b, :c]  
m2 |> Map.keys # => [:a, :b, :c]  

According to the rules of lists, the 2 maps key sets are equal. Notice that m2's keys were sorted.

After looking at the keys, the values are compared next. We know that Elixir sorted the map based on the keys, so now we're comparing:

m1 |> Map.values #=> [1, 2, 3]  
m2 |> Map.values #=> [3, 2, 1]  

Based on the rules of lists, m2 is greater than m1

So now you know

Why would you need to compare things like maps? You probably won't and if you think you do, then you're probably missing something. Ever used the Calendar module added in Elixir 1.3, though? Try comparing 2 DateTime values and you'll quickly find that weird things happen. As it turns out, the DateTime module is just a map underneath, and therefore doesn't act like a date in comparisons. Instead you need to use the various comparison functions in the Calendar module.

Now to find another cup of coffee.

Dave Long

Read more posts by this author.

Subscribe to Dave Long

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!