Today we are going to learn about the usual operators that are essential for being able to use a programming language. After learning many languages with similar operators, I find this particular topic to be an essential, but boring, part of learning a new language. Fortunately, there aren't a lot of surprises or weirdness here. There is some Elixir uniqueness, but for the most part, these operators are a lot like those in other languages I've used.
Arithmetic operators are the basic mathematical operators.
There are operators for addition, subtraction, multiplication and division (
Let's take a look at some examples in IEx.
iex> 6 + 3 9 iex> 6 - 3 3 iex> 6 * 3 18 iex> 6 / 3 2.0
iex> 4 + 3 * 5 19 iex> (4 + 3) * 5 35
iex> 5 / 2 2.5 iex> div(5, 2) 2 iex> 8 / 2 4.0 iex> div(8, 2) 4
The following is equivalent to
5 % 2 and
iex> rem(5, 2) 1 iex> rem(28, 10) 8
There is one difference between
rem/2 and the usual modulo operations in other languages. The sign of the result will be the same sign of the first argument, unless that result is 0 of course.
iex> rem(-10, 10) 0 iex> rem(-10, 12) -10 iex> rem(10, 12) 10 iex> rem(-100, 12) -4
==) and triple equals (
The triple equals operator (
===) will test to see if the two things being compared are of the same type, and if they are, if they are the exact same value. Let's look at some examples.
iex> "Bob" === 4 false iex> 4 === 4 true iex> "Bob" === "Bob" true iex> 4.0 === 4 false iex> true === "true" false iex> 3.5 === :cheese false iex> :chees === :cheese false iex> :cheese === :cheese true
Yes, I left my mispelling of
:chees in there to show that any difference in the name of an atom means that they are two different atoms entirely.
The double equals operator (
==) is almost exactly the same. The only difference is that is will coerce floats and integers so that they can be compared to each other.
iex> 5 === 5.0 false iex> 5 == 5.0 true iex> 5 == 5.000000001 false iex> "true" == true false iex> 1 == true false iex> 0 == false false
C#, on the other hand, being a statically-typed language, doesn't even allow different data types to be compared unless there's a method available that specifically implements that exact comparison. That's a difference between a statically-typed and a dynamically-typed language.
There are equivalent versions of the not-equals operator:
!=, the opposite of
!==, the opposite of
== unless you need to distinguish floating point numbers from integer numbers.
Equals method that contains the code to do the comparison.
I didn't see anything that specifically addressed this in relation to Elixir, so I did some experimentation. In my experimentation, I found that a data structure always seems be equal to an identical data structure.
I suspect that identical data structures created in separate places at separate times will just be references to the exact same data in the internals of Elixir. I haven't read anything to indicate that this is the case, but that would make perfect sense, since all data in Elixir is immutable. The language would never have to worry about the same instance data being modified, so it doesn't ever need to store multiple copies of identical data.
This is similar to how strings work in C#. There's a table of every string in use, and all variables referring to the same string point to the exact same string instances. I don't think it's widely known, but strings in C# are actually immutable. All string operations result in a new string instance. This immutability saves memory, since there are never two instances of the same string. That appears to be exactly how Elixir operates, except with with all data, not just strings.
<=. Integers and floats can be compared to each other.
iex> 4 < 4.0 false iex> 4 > 4.0 false iex> 5 > 4.0 true iex> 5 < 4.0 false iex> 4.0001 > 4 true iex> 3 >= 3 true iex> 3 <= 3 true
Comparing Different Data Types
Different data types can be compared to each other in Elixir, but the result is mostly useless.
iex> true < 4 false iex> 4 < true true iex> "Bob" > :ok true iex> "Bob" < :ok false
Elixir allows this so that collections containing elements of different data types can be sorted consistently. There are also some other similar scenarios where different data types need to be ordered. According to the Elixir documentation, comparisons between data types always follow the following rule.
number < atom < reference < function < port < pid < tuple < map < list < bitstring
It's irrelevant what the value of a data type is when you are comparing different data types: it's the data types themselves that determine the result of the comparison.
Boolean operators are necessary when creating boolean expressions. Elixir has two sets of boolean operators. Like with the equality operators, there is a stricter set of operators and a less-strict set of operators.
|| (or), and
! operators have a concept of truthiness, where when you use these boolean operators, all data types are automatically be converted to a "truthy" value, where they function like a
true boolean value, or a "falsely" value, where they function like a
false boolean value.
In Elixir, a boolean
false value and a
nil value (the equivalent of
|| boolean operators will return one of the values in the expression instead of
! operator will always convert a value to
An expression with the
&& operator will evaluate to the first value if the first value is falsey and evaluate to the second value if the first value is truthy. The
|| operator is the opposite: the expression will evaluate to the second value if the first value is falsey and will evaluate to the first value if the first value is truthy.
If that sounded confusing, it will probably help to look at some examples. I added in some comments to describe what is happening.
Now let's take a look at the stricter set of boolean operators. These operators are
or (or), and
not (not). The difference between this set of operators and the previous set of operators is that they only operate on boolean values and not anything else. Someone familiar with Java or C# will feel more comfortable with these operators.
Well, actually only the first argument has to be a boolean value. The second argument can be any value, and the expression will resolve to the second value if the first value is false (for "or" operations) or if the first value is true (for "and" operations). The second value can be any data type because it has no bearing in which argument is returned from the expression (the first or the second): only the first value determines that.
Let's look at some examples.
iex> true or false true iex> false or true true iex> true and false false iex> false and true false iex> false and false false iex> true and true true iex> true or "3" true iex> false or "3" "3" iex> "1" or 5 ** (BadBooleanError) expected a boolean on left-side of "or", got: "1" iex> true and 10 10 iex> false and 10 false iex> not false true iex> not true false iex> not 0 ** (ArgumentError) argument error :erlang.not(0) iex> not "Bob" ** (ArgumentError) argument error :erlang.not("Bob")
Despite the ability to make the second argument to
or a non-boolean value, the Elixir community recommends that you use this set of operators only when you expect boolean values.
Both sets of binary boolean operators (
or) are short-circuiting operators. That means that if the first argument to a boolean
and operation evaluates to false, the second argument will not be evaluated at all. An
is_available?(resource) && do_something_with_resource(resource) is_available?(resource) || acquire_resource(resource)
The first line will only call do_something_with_resource if the resource is available. The second line will only call acquire_resource if the resource is not available.
It's possible to redefine operators in Elixir to do something else. There's also a list of operators that Elixir can parse and recognize as operators, but it doesn't do anything with them. I suspect that those unused operators are meant for possible use in the future. You can associate functionality with those unused operators as well.
The Elixir community, however, discourages redefining operators, unused or not. The Elixir community emphasizes clear, readable code, and unusual operators or unexpected operator behavior generally works against that goal. The correct solution is usually to just create a clearly-named function that accomplishes the same thing.