Basic Types Overview
There aren’t that many basic data types in Elixir. These are the ones we’ll cover here.
- atom
- boolean
- nil
- integer
- float
- string
Remember, we learn best by doing. Open an IEx shell and play with each of these types as we cover them.
Contents
Atom
An atom is a literal, a constant with name. It is a constant whose name is its value. Atoms are very useful in pattern matching and are used for more than you might expect in Elixir.
Examples of Atoms:
:my_atom
:testing
:customer_type
An atom begins with a colon and typically is all lowercase letters using an underscore to separate words.
If you need an atom to contain spaces or special characters that otherwise wouldn’t be valid, you can express it as a string with the colon in front.
:"I'm still an atom"
However, writing atoms this way is unusual and only done when needed for specific exceptional instances.
An Atom in Elixir is similar to a symbol in Ruby or an enumeration in C/C++. Atoms are stored in the “atom table”. All references to an atom like “:ok
” are shared and actually all point to the same atom table entry.
Functions for working with an Atom can be found in the Atom module.
Preventing Denial-of-Service
Atoms are not garbage collected. The important thing to learn from this is you should not allow user provided content to become an atom at run-time in your system. Converting unchecked user data to an atom can expose your system to a Denial of Service (DOS) attack. The attack works like this, a malicious actor causes your system to repeatedly create unique atoms until it consumes all of the available resources on your machine ultimately causing it to crash. It isn’t a security flaw, it isn’t a “break-in”, but it can be abused to cause your system to crash.
You can allow user input to become an atom using the String.to_existing_atom/1
. If you already have an atom defined in the system, it accepts the conversion. If you try to convert to something new, it raises an exception blocking the operation.
iex(1)> String.to_existing_atom("ok")
:ok
iex(2)> String.to_existing_atom("whaaaat")
** (ArgumentError) argument error
:erlang.binary_to_existing_atom("whaaaat", :utf8)
Boolean
Examples are: true
and false
.
The boolean values are actually implemented as special reserved atoms.
false == :false
#=> true
true == :true
#=> true
Nil
Nil represents the absence of a value. It is like null
in Javascript and many other languages.
Interestingly, nil
is also implemented as a reserved atom.
nil == :nil
#=> true
In evaluations, nil
behaves like false
.
if nil || true, do: "True!", else: "False."
#=> "True!"
Integer
Represents positive and negative whole numbers. Including 0.
Functions for working with an Integer can be found in the Integer module.
Examples:
-10, 0, 1000
Integers can be arbitrarily large. It uses as much data is needed to contain it.
99999999999999999999999999999999999999999999999999999999
Can be represent as Hex and more.
0xFF
#=> 255
Integers can use _
as a grouping separator. This is functionally ignored but helps with readability.
1_000_000
#=> 1000000
Float
Represents floating point numbers.
Examples:
-12.4, 0.5, 100.67, 83.33333333333
Functions for working with a Float can be found in the Float module.
Floats don’t support a “Decimal” type for explicit levels of precision. As such, it results in typical floating point rounding issues.
0.8 * 3
#=> 2.4000000000000004
This is expected with floating point math. For further explanation, see these resources:
If you need to represent fixed decimal values for something like money, using a Float may not be the most appropriate choice. For example, it may work better to use an integer that represents cents (instead of whole dollars).
Another option is to use a community package called Decimal that provides arbitrary decimal precision.
Scientific Notation
Floats are frequently displayed in their scientific notation. This may not be what you expect or want.
1200.0
#=> 1.2e3
Note that when working in IEx, it converts the data to a string for display in the shell. This is the same display as Float.to_string/1.
Float.to_string(1200.0)
#=> "1.2e3"
If you need to convert a float to a string with explicit decimal precision, use the built-in Erlang function float_to_binary
. In Elixir, you can use any Erlang function. In this example, the function we want is declared in the erlang
module. To call it, use an atom as the module name like this:
:erlang.float_to_binary(1200.0, decimals: 2)
#=> "1200.00"
String
Strings are encoded in UTF-8. A string uses the double quote character "
. Single quoted text is not a string, that’s a charlist and behaves differently.
"The quick brown fox"
The String module has a number of functions for working with them.
String.upcase("hello world")
#=> "HELLO WORLD"
Concatenation
Elixir strings can be concatenated using the <>
operator.
"one" <> " two"
#=> "one two"
text = "Hey"
text <> " friends!"
#=> "Hey friends!"
Interpolation
Elixir strings support interpolation using the #{...}
characters embedded in a string.
name = "Tom"
"Greetings #{name}!"
#=> "Greetings Tom!"
Strings are Binary
UTF-8 strings are a more recent addition to Erlang. Because of this, many Erlang functions that take a string don’t take an Elixir string. Erlang sees an Elixir string as a binary
type.
is_binary("a string")
#=> true
In Erlang, functions often expect a charlist
.
Charlist
Mostly used for interoperability with Erlang. It is just a list of code points and is created with single quotes.
'this is a charlist'
#=> 'this is a charlist'
is_list('testing')
#=> true
A charlist can be converted to a string and vise versa using these functions:
“Kernel” is Elixir’s default environment. It even defines basic math operators like +
and -
. Because it is so essential to Elixir, the Kernel module is always included for you. You can just use the functions to_string/1
and to_charlist/1
to convert between the types without needing to specify the “Kernel” module explicitly.
to_string('hello world')
#=> "hello world"
to_charlist("hello world")
#=> 'hello world'
Modules to Manipulate
All of these types are primitives. They are “data”. They are not objects with functions attached to them. There are no “objects” in Elixir. There is only “data” and “functions”. Modules are a collection or a container for functions.
By convention, when you want to perform some function on a piece of data, you use the type’s module for doing that. This is particularly the case for Atom, Integer, Float, and String.
4 Comments
Leave a Comment
You must be logged in to post a comment.
This lesson is short,
But it explains a lot! )
“An atom is a literal, a constant with name. It is a constant whose name is its value.”
Best explanation of an Atom that I have seen so far!
An Open question, When should we go for Charlist vs String?
Hi Venkat!
You almost certainly never want a charlist in your Elixir code. The only times I can think of are:
– you are working directly with an Erlang library that takes a charlist. If so, work with strings and convert it when passing.
– you are working with a file format (or binary format) that best fits a charlist. Again, I’d convert it to work with the data though.
Hope that helps!