Elixir from the ground up

Values: numbers, text and atoms

The three values you will use most: whole numbers and decimal fractions, text, and atoms — the little labels that mean themselves.

Part 2 of 12

Beginner15 min readBack to Courses

Numbers are exact until you ask otherwise

A whole number in Elixir is an integer, and it is not limited to the size of the machine — a number with a hundred digits is an ordinary integer here. A decimal fraction is a float, and floats are stored in a fixed amount of space, so most of them are only close to the value you typed. An integer grows to fit the number, which is why a count can pass a million million without a special type; a float has a fixed number of bits and stops being exact when the value needs more.

The slash always produces a float. div gives the whole part of a division and rem gives the remainder, which is how you tell whether one number is a multiple of another: rem(n, 2) is zero for even numbers. You may write underscores inside a long number as reading aids — 1_000_000 is a million. The underscore is only a reading aid: the reader removes it, and the value is the same as if you had written the digits together.

Atoms: names that mean themselves

An atom is a name that is its own value, written with a colon in front: :ok, :error, :pending. Two atoms with the same name are the same value, so they are the cheapest and clearest way to say which case you are in. An atom is stored once by the runtime and compared by identity, which is why a match on :ok costs nothing and reads as plainly as a word.

You will meet them constantly, because the standard library uses them for answers: a function that might fail returns {:ok, value} or {:error, reason}. You are free to invent your own — :pending above is nothing special, just a name you chose.

Text is a string, and String holds the tools

Text goes in double quotes. The <> operator joins two strings into a new one, and the String module has the rest: String.upcase, String.length, String.reverse, String.split, String.trim. There are no methods hanging off a value in Elixir — a string does not know how to upper-case itself, so you hand it to the function that does. That is the whole shape of the standard library: a module is a toolbox, and the value is the first argument you hand it. It also means two modules can offer the same function name without stepping on each other.

Single quotes are not strings in Elixir. 'hi' is a different and older kind of text from Erlang, and it is best avoided entirely while you are learning: use double quotes for every piece of text and you will never be caught out by it.

Atoms, numbers and the String module

# An atom is a name that means itself.
IO.inspect(:ok)
IO.inspect(:error == :error)

status = :pending
IO.puts("status is #{status}")

# Integers are exact, floats can be close.
IO.inspect(div(7, 2))
IO.inspect(rem(7, 2))
IO.inspect(7 / 2)
IO.inspect(1_000_000)

# A string holds text; String holds the tools.
text = "Elixir"
IO.inspect(text <> " rocks")
IO.inspect(String.upcase(text))
IO.inspect(String.length(text))
IO.inspect(String.reverse(text))

Values, and what they say about themselves

Run it, then add an atom of your own, print the remainder of 17 divided by 5, and join two strings with the joining operator.

Output
Click "Run" to execute your code

IO.inspect prints an atom with its colon and a string with its quotes, so you can always tell from the output which one you are holding. Printing a float that came out of a division is the other fast check: 7 / 2 shows 3.5, div(7, 2) shows 3.

What text is made of

Elixir text is UTF-8, so a string is a sequence of characters rather than a row of bytes. String.length("café") answers 4 because it counts characters; byte_size("café") answers 5 because the é takes two bytes. The difference matters as soon as the text comes from a person rather than from a keyboard you control. It also explains why reversing text with an accent can look wrong in a tool that works byte by byte: String works in characters, and byte_size is the measurement for storage.

An atom is not text with a colon in front. It is a name the runtime stores once and reuses, which is why comparing two atoms is instant. That permanence is also the danger: never build an atom from text a visitor typed, because atoms are kept for the life of the runtime and a program can fill that table with names it will never use again. The safe conversion is String.to_existing_atom, which only accepts a name the program already knows; anything else is data and should stay a string.

+ adds numbers and <> joins text, so mixing them raises an error. String.length counts characters while byte_size counts bytes, so the two disagree on accented text. And single quotes make a charlist rather than a string: IO.inspect shows it with single quotes, and a String function rejects it.

Try these next

  • Take the sentence " Elixir values are simple " and print it trimmed, upcased, and split into words.
  • Make an atom for each day of a working week and print one of them with IO.inspect.
  • Work out the total of three prices and print it rounded to two decimal places.

A price tag and a word count

Run it and check the cleaned sentence against the original. Then change the sentence, add a word of your own, and print the total for a different price and quantity.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What is an atom such as :pending?