Lesson 3 / 25

Basic Types and Immutability

Use atoms, numbers, strings, lists, tuples, maps and keyword lists.

Elixir's data types

Integers have arbitrary precision and floats are 64-bit; / always returns a float, while div and rem do integer division. Atoms are named constants whose value is their name (:ok, :error, :pending); true, false and nil are atoms too, and module names are atoms. Strings are UTF-8 binaries in double quotes with interpolation ("Hi #{name}"); String.length counts characters while byte_size counts bytes. Single-quoted 'abc' is a charlist (a list of code points), mainly for Erlang interop, and is written ~c"abc" in modern code. Lists [1, 2, 3] are linked lists, fast to prepend with [0 | list] and slow to index. Tuples {:ok, value} are fixed-size, contiguous and used for return values. Maps %{"pune" => 2} or %{city: "Pune"} are key-value stores, accessed with map.city (atom keys, raises if missing) or map[:city] (returns nil). Keyword lists [timeout: 5000, retries: 3] are lists of {atom, value} tuples used for options. All data is immutable: functions return new values, and rebinding a variable name does not change the old value.

Working with basic types

Atoms, maps, tuples and immutable updates.

status = :paid
IO.puts(status == :paid)                 # true

IO.puts(7 / 2)                            # 3.5 (always a float)
IO.puts(div(7, 2))                        # 3
IO.puts(rem(7, 2))                        # 1

name = "Ananya"
IO.puts("Hello #{name}")
IO.puts(String.length("नमस्ते"))           # characters
IO.puts(byte_size("नमस्ते"))               # bytes

order = %{id: "o-1", city: "Pune", total: 1200}
IO.puts(order.city)                       # Pune
IO.inspect(order[:discount])              # nil

updated = %{order | total: 1500}          # update an existing key (new map)
with_note = Map.put(order, :note, "gift") # add a key (new map)
IO.inspect(order.total)                   # still 1200: data is immutable

items = ["pen", "ink"]
more = ["pad" | items]                    # prepend: ["pad", "pen", "ink"]

result = {:ok, updated}
opts = [timeout: 5_000, retries: 3]
IO.inspect(Keyword.get(opts, :retries))   # 3
IO.inspect(with_note)
IO.inspect(more)
IO.inspect(result)

Rebinding is not mutation

x = 1 followed by x = 2 rebinds the name x to a new value. Any closure or process that captured the old value still sees 1. Values themselves never change.

Quick check: What does `%{order | total: 1500}` do?

  • Mutates order in place
  • Adds a new key only
  • Returns a new map with total updated, raising if total is not already a key
  • Deletes the total key
Answer

Returns a new map with total updated, raising if total is not already a key — The update syntax creates a new map and requires the key to exist.