Erlang from the ground up

Values: atoms, numbers and text

The handful of values Erlang has — atoms, whole numbers, decimals, strings and booleans — and the two different divisions and two different ways of printing text.

Part 2 of 12

Beginner15 min readBack to Courses

Four kinds of value

An atom is a fixed label, written in lowercase, like blue, ok or error. It is not a variable: blue means the atom blue wherever it appears, and two atoms are the same value if they are spelled the same. Booleans are atoms too — true and false — which is why they print without quotes.

Numbers come in two forms. Whole numbers are exact and unbounded, so a factorial will keep growing rather than wrapping round. Decimal fractions are written with a point, like 3.5, and mixing the two gives a fraction: 2 + 2 is the integer 4, while 2.0 + 2 is the fraction 4.0.

Text is written in double quotes. "abc" is a string, and a string is a list of character codes, so "hi" and [104, 105] are the same value written two ways. That detail explains most of the surprises in this chapter.

Two divisions, two remainders

The slash always gives a decimal fraction, even for two whole numbers: 10 / 4 is 2.5. When you want the whole part, ask for it by name: 10 div 4 is 2. The leftover is rem, so 10 rem 4 is 2 as well. Between them, div and rem say exactly what integer arithmetic you meant.

The usual operators do the usual things: plus, minus, times, and comparison with == for equal values. A comparison gives the atom true or the atom false, never a number.

div truncates toward zero, so -7 div 2 is -3, and rem takes the sign of the left operand, so -7 rem 2 is -1. Both are exact on whole numbers of any size, and neither can produce a fraction. When you need the mathematical modulus instead, rem is not it for negative values: check the sign the problem expects before choosing.

Two ways to print text

~s prints a string as people read it, so "abc" comes out as abc. ~p prints the raw value, so the same string comes out as "abc" with its quotes. This is not a quirk: ~p is telling the truth about the value, and the value really is a list of numbers.

The two controls are why the same program can show a friendly line to a reader and a precise value to a programmer. byte_size counts the bytes of a string, and ++ joins two strings end to end.

Atoms, numbers and text side by side

-module(prog).
-export([main/1]).

main(_) ->
    io:format("atom ~p~n", [blue]),
    io:format("int ~p~n", [42]),
    io:format("float ~p~n", [3.5]),
    io:format("mixed ~p~n", [2.0 + 2]),
    io:format("slash ~p~n", [10 / 4]),
    io:format("div ~p rem ~p~n", [10 div 4, 10 rem 4]),
    io:format("joined ~s~n", ["ab" ++ "cd"]),
    io:format("as text ~s~n", ["hi"]),
    io:format("as value ~p~n", ["hi"]),
    io:format("codes ~p~n", [[104, 105]]),
    io:format("bool ~p ~p~n", [true, 1 == 2]).

Every kind of value on one screen

Run it and match each line to the value in the code. Then add a line that prints `2 + 2` and a line that prints `2.0 + 2` and notice the difference.

Output
Click "Run" to execute your code

A string is a list, so [104, 105] and "hi" are one and the same value: print either with ~p and both come out as "hi". Reach for ~s when you want the text a reader sees, and ~p when you want to see what the value actually is.

Two kinds of equality

Erlang has two equality tests, and they answer different questions. == compares values after converting numbers, so 1 == 1.0 is true. =:= compares both the value and its type, so 1 =:= 1.0 is false. Pattern matching uses the exact form: a pattern holding the integer 1 will not match the float 1.0.

That distinction matters at the edges of a program, where a value may arrive as either an integer or a float. Use =:= when the type is part of the answer, and == only when a whole number and its decimal form really should count as the same thing.

A string is a list you can walk

Because "hi" is the list [104, 105], every list function works on it. hd("hi") is the first character code, length("hi") is 2, and lists:reverse("hi") is "ih". The quotes are a convenience for writing a list of printable codes, not a separate type.

A single character is written with a dollar sign: $a is 97. That is why [$h, $i] and "hi" are the same value, and why building text with the pipe works: [$H | Tail] puts one character in front of a string. byte_size counts bytes rather than characters, which only differs once the text leaves plain ASCII.

Three traps sit in this chapter. = is a match, not a comparison, so X = 3 where X already holds 4 stops the program; use == or =:= to ask. The slash always gives a fraction, so 10 / 2 is 5.0, not 5; div and rem are operators written between their operands, as in 17 div 5, and they keep whole numbers whole. And a number is not its text: 42 and "42" are different values, and ~s given a number fails.

Try these next

  • Print 1 == 1.0 and 1 =:= 1.0 on two lines and explain the difference.
  • Take "hello" apart with hd, length and lists:reverse, printing each answer.
  • Work out a bill with a float price and a whole quantity, then print the total with ~.2f.

A price list with exact and decimal numbers

Run it and check each total by hand. Then add a fourth line for a loaf and print the average price of the four items with one decimal place.

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 blue in the expression [blue]?