Learn With Me: Elixir - Arithmetic, Comparison, and Boolean Operators (#7)
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
Arithmetic operators are the basic mathematical operators.
There are operators for addition, subtraction, multiplication and division (+
, -
, *
, /
), which are the same as in Javascript, C#, or indeed most languages I've ever used. There are no other mathematical operators, so there's none of the increment or decrement operators that are common in Javascript and C#. All other mathematical operations need to be accomplished through functions.
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
The order of operations is the same in math and every other programming language I've ever seen. Like with Javascript and C#, parentheses can be used to override the order of operations or provide grouping to help make calculations easier to read.
iex> 4 + 3 * 5
19
iex> (4 + 3) * 5
35
In Elixir, division using the division operator (/) always results in a float no matter whether the operands are integers or floats. So 5 / 2 is 2.5 and 8 / 2 is 4.0. Javascript behaves similarly, but Javascript has a single numerical data type. C# does not do this. In C#, the result of dividing two integers results in an integer. If you want integer division, Elixir makes that available through the div
function.
iex> 5 / 2
2.5
iex> div(5, 2)
2
iex> 8 / 2
4.0
iex> div(8, 2)
4
There's no modulo operator in Elixir like Javascript or C#. The same operation can be accomplished by using the rem
function.
The following is equivalent to 5 % 2
and 28 % 10
in C# or Javascript.
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
Comparison Operators
Comparison operators also look a lot like those in C# and Javascript, but there are a few differences we need to be aware of.
Equality
Like Javascript, Elixir has two equality comparison operators, the double equals (==
) and triple equals (===
). Like Javascript, the triple equals is stricter than the double equals, but unlike Javascript, the difference in strictness is very small.
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
There's none of that complex type coercion nonsense that happens in Javascript, where you have to remember all the rules. It just allows floats to be equal to integers if they both represent the same number.
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 ==
, and !==
, the opposite of ===
. These operators look like the equivalent operators in Javascript operators.
So unlike Javascript, it doesn't look like we need to always use the stricter version of the equals operator in order to prevent unexpected type conversion. I recommend defaulting to using double equals ==
unless you need to distinguish floating point numbers from integer numbers.
From what I've seen so far, we don't have to worry about comparing different instances of the same data, like we do in C# or Javascript. One object in C# or Javascript will not be equal to another object containing identical data unless (at least in C#) we implement a special 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.
Comparing Numbers
As with C# and Javascript, you can compare numbers to each other to see if one number is greater than the other. The operators are also the same: >
, >=
, <
, <=
. 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
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.
Less-strict Operators
Let's take a look at the less-strict set of operators. These operators look just like they do in Javascript, C#, and a lot of other C-like languages. There is &&
(and), ||
(or), and !
(not).
Like Javascript, the &&
, ||
, 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 null
in C# or Javascript) are falsey. Everything else is truthy. This is much simpler definition than in Javascript, where a 0 and an empty string (among other values) can also be falsey. So if you're used to Javascript's rules, you will have to adjust to Elixir's rules. If you're used to boolean operators in C# and other similar languages, which operate strictly on boolean values, the concept of a "truthy" of "falsey" value will seem a bit weird.
In Elixir (much like Javascript), an expression involving the &&
and ||
boolean operators will return one of the values in the expression instead of true
and false
. The !
operator will always convert a value to true
or false
.
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.
This will seem weird if you've never used a language that works this way. You'll have an advantage in understanding this if you know the boolean operators in Javascript well.
If that sounded confusing, it will probably help to look at some examples. I added in some comments to describe what is happening.
#"Bob" is truthy, so the expression evaluates to 1
iex> "Bob" && 1
1
#"Bob" is truthy, so the expression evaluates to false
iex> "Bob" && false
false
#"Bob" is truthy, so the expression evaluates to nil
iex> "Bob" && nil
nil
#1 is truthy, so the expression evaluates to "Bob"
iex> 1 && "Bob"
"Bob"
#false is falsey, so the expression evaluates to false
iex> false && "Bob"
false
#true is truthy, so the expression evaluates to "Bob"
iex> true && "Bob"
"Bob"
#nil is falsey, so the expression evaluates to nil
iex> nil && "Bob"
nil
#"false is falsey, so the expression evaluates to true
iex> false || true
true
#true is truthy, so the expression evaluates to false
iex> true || false
true
#0 is truthy (unlike Javascript), so the expression evaluates to false
iex> !0
false
#1 is truthy, so the expression evaluates to false
iex> !1
false
#false is falsey, so the expression evaluates to true
iex> !false
true
#nil is falsey, so the expression evaluates to true
iex> !nil
true
#"1" is truthy, so the expression evaluates to "1"
iex> "1" || 3
"1"
#true is truthy, so the expression evaluates to 18
iex> true && 18
18
#false is falsey, so the expression evaluates to :pencil
iex> false || :pencil
:pencil
#nil is falsey, so the expression evaluates to "spigot"
iex> nil && "spigot"
nil
#"1" is truthy, so the expression evaluates to false
iex> !"1"
false
Stricter Operators
Now let's take a look at the stricter set of boolean operators. These operators are and
(and), 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 and
and 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 (&&
, ||
, and
, 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 or
operation will not evaluate the second argument if the first argument evaluates to true. This corresponds to the behavior of boolean operators in Javascript and C#, and programmers will use this to their advantage.
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.
Redefining Operators
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.