Powered by AppSignal & Oban Pro

02 - Data Types

02-data_types.livemd

02 - Data Types

Two groups

Base Types and High Level Types (which are made from Base Types)

Base types

Numbers

Integers (no decimal places) Floats (have decimal places)

Atoms

Named constant. Most often used for naming things.

# how to write atoms
:this_is_an_atom
:"An atom with spaces"

# Module names are atoms
DateTime
# => true
DateTime == :DateTime

# True and False are atoms
# => true
true == true
# => true
false == false

# Nil is an atom
# => true
nil == nil

Binaries

Represents a series of bytes which can be mapped to a number or a value.

Examples:

# The binary list
[01_101_000, 01_100_101, 01_101_100, 01_101_100, 01_101_111]

# is represented in Elixir as this Binary
<<104, 101, 108, 108, 111>>

# which can also be written this way
"hello"

Strings

Are just binaries using a different notation

Examples:

# Strings are just binaries
"hello" == <<104, 101, 108, 108, 111>>

"""
This is a multiline string.
They are used in documentation.
"""

Maps

Key-Value stores. %{key: value, ...}

Examples:

episode = %{
  name: "Data types",
  author: "Daniel Berkompas"
}

# => "Data types"
episode.name
# => "Daniel Berkompas"
episode[:author]

CAVEAT

# If you use Strings for keys you can't use the dot notation
# for retrieving values
episode = %{
  "name" => "Data types",
  "author" => "Daniel Berkompas"
}

# episode.name      #=> ERROR: doesn't work
# => "Daniel Berkompas"
episode["author"]

> CAVEAT [circa 1.2] > “Maps can now scale from dozens to millions of keys. Therefore, usage of the modules Dict and HashDict is now discouraged and will be deprecated in future releases, instead use Map. Similarly, Set and HashSet will be deprecated in favor of MapSet”

Tuples

A collection of items. Usually used to hold together a fixed number of items.

Examples:

me = {"Daniel", 24}

# how to access elements in a Tuple
# => "Daniel"
elem(me, 0)
# => 24
elem(me, 1)

# how to update an element in an existing Tuple
# => {"Daniel", 25}
put_elem(me, 1, 25)

Lists

Variable length collection. Like Tuples they can contain any types of values. Similar to arrays in other languages.

Examples:

ages = [42, 31, 24]
names = ["Ash", "Leslie", "Dori"]

# => "Ash"
Enum.at(names, 0)

> CAVEAT > Implemented as immutable head/tail pairs (also known as a singly-linked list). > This means that it’s very FAST to add an element to the front of the list.

list1 = [1, 2, 3]
# => [0, 1, 2, 3]
list2 = [0 | list1]

This is because all you are doing is creating a new element with a link to the first element of list1. list1 doesn’t change (and never will because it’s immutable), so list2 just contains a link to list1, it doesn’t contain its own copy.

However it is potentially SLOW to add an element to the end of the list.

list1 = [1, 2, 3]
list2 = list1 ++ [4]

This is slower because in order to add element 4 to the end of the list you need to modify the tail of element 3. However you can’t do this because element 3 is immutable, so you need to make a copy of it in list2. This proliferates right to the start of list2, thus making it a potentially very expensive operation.

Inserting an element into the middle of a list is similarly expensive.

Summary

Lists:

  • are head/tail pairs
  • immutability makes them memory efficient
  • prepending is FAST
  • appending is SLOW
  • inserting elements can also be slow
  • reading the whole list can be slow

Character lists

[integer, integer, ...]

Examples:

# shorthand notation
~c"hello"

# sigil notation
~c"hello"

# under the hood
[104, 101, 108, 108, 111]

> IMPORTANT > Unless you are working with an Erlang library, use Binaries instead!

Functions

fn(args) -> ... end

Examples:

add = fn a, b -> a + b end

# => 3
add.(1, 2)

Other basic types

  • PIDs
  • References
  • Records
  • Port references

How to check types

is_type(value)

Examples:

Enum.all?([
  is_atom(:hello),
  is_list([1, 2, 3]),
  is_map(%{key: "value"})
])

High Level Types

Not true types. They are constructs build from basic types.

Keyword lists

[{:atom, value}, ...]

Examples:

# shorthand notation
attrs = [name: "Daniel Berkompas", email: "test@example.com"]

# under the hood
[{:name, "Daniel Berkompas"}, {:email, "test@example.com"}]

# you can access values in a keyword list by their label
# => "Daniel Berkmompas"
attrs[:name]
# => "test@example.com"
attrs[:email]

> CAVEAT > Bear in mind that keyword lists are lists and have all of the pros and cons associated with them.

Structs

%{__struct__: ModuleName, ...}

Examples:

defmodule Episode do
  defstruct [:title, :author]
end
# shorthand notation (can't get this to run in LiveBook)
%Episode{
  title: "Data Types",
  author: "Daniel Berkompas"
}

# under the hood
%{
  __struct__: Episode,
  title: "Data Types",
  author: "Daniel Berkompas"
}

Range

%Range{first: number, last: number}

Examples:

# shorthand notation
0..100

# under the hood
%Range{
  first: 0,
  last: 100,
  step: 1
}

Regular expressions

%Regex{opts: ..., re_pattern: ...}

Examples:

# shorthand notation
~r/hello/

# under the hood
# hint: we can find the binary representation used in the `re_pattern`
# by using `IO.inspect("hello", binaries: :as_binaries)`
%Regex{
  opts: "",
  re_pattern: {:re_pattern, <<104, 101, 108, 108, 111>>},
  source: "hello"
}

Other high level types

  • Tasks
  • Agents
  • Streams
  • HashDicts (don’t use)
  • HashSets (don’t use)