Базовые типы данных elixir, и их неизменяемость
НЕДЕЛЯ 1: Основы Elixir и функционального программирования
Тема 2: Базовые типы данных и неизменяемость (3 часа)
2.1 Базовые типы данных
Elixir имеет следующие базовые типы:
Integers (Целые числа)
☯
Terminal:
⌕
≡
✕
# Десятичные
iex> 42
42
# Бинарные
iex> 0b1010
10
# Восьмеричные
iex> 0o777
511
# Шестнадцатеричные
iex> 0xFF
255
# Можно использовать underscore для читаемости
iex> 1_000_000
1000000
# Операции
iex> 10 + 5
15
iex> 10 - 5
5
iex> 10 * 5
50
iex> div(10, 3) # Целочисленное деление
3
iex> rem(10, 3) # Остаток от деления
1
Floats (Числа с плавающей точкой)
☯
Terminal:
⌕
≡
✕
iex> 3.14
3.14
iex> 1.0e-10
1.0e-10
# Операции
iex> 10 / 3
3.3333333333333335
iex> Float.round(3.14159, 2)
3.14
iex> Float.ceil(3.14)
4.0
iex> Float.floor(3.14)
3.0
Важно: В Elixir оператор / всегда возвращает float!
Booleans (Булевы значения)
☯
Terminal:
⌕
≡
✕
iex> true
true
iex> false
false
iex> is_boolean(true)
true
# Логические операции
iex> true and false
false
iex> true or false
true
iex> not true
false
# Операторы || && ! работают с любыми типами
iex> 1 || 2
1
iex> false || 42
42
iex> nil && 13
nil
Важно: В Elixir только false и nil считаются falsy. Всё остальное - truthy (включая 0 и пустые строки)!
Atoms (Атомы)
Атомы - это константы, имя которых является их значением.
☯
Terminal:
⌕
≡
✕
iex> :apple
:apple
iex> :orange
:orange
iex> :error
:error
# true, false и nil - тоже атомы
iex> true == :true
true
iex> is_atom(false)
true
iex> is_boolean(:false)
true
# Атомы часто используются для тегирования значений
iex> {:ok, "Success"}
{:ok, "Success"}
iex> {:error, "Something went wrong"}
{:error, "Something went wrong"}
Применение атомов:
- Ключи в maps
- Статусы операций (
:ok,:error) - Опции функций
- Идентификаторы модулей
Strings (Строки)
☯
Terminal:
⌕
≡
✕
# Строки в двойных кавычках - UTF-8 encoded binaries
iex> "hello"
"hello"
iex> "привет"
"привет"
iex> "hello
...> world"
"hello\nworld"
# Интерполяция
iex> name = "Elixir"
iex> "Hello, #{name}!"
"Hello, Elixir!"
# Конкатенация
iex> "Hello" <> " " <> "World"
"Hello World"
# Длина строки
iex> String.length("привет")
6
# Проверка типа
iex> is_binary("hello")
true
# Uppercase/Lowercase
iex> String.upcase("hello")
"HELLO"
iex> String.downcase("HELLO")
"hello"
# Разделение
iex> String.split("hello world", " ")
["hello", "world"]
# Обрезка пробелов
iex> String.trim(" hello ")
"hello"
# Проверка на содержание
iex> String.contains?("hello world", "world")
true
Charlists - списки символов (редко используются):
☯
Terminal:
⌕
≡
✕
iex> 'hello'
'hello'
iex> [104, 101, 108, 108, 111]
'hello'
iex> is_list('hello')
true
2.2 Составные типы данных
Tuples (Кортежи)
Кортежи хранят элементы в смежной памяти.
☯
Terminal:
⌕
≡
✕
# Создание кортежа
iex> {:ok, "hello"}
{:ok, "hello"}
iex> tuple = {1, 2, 3}
{1, 2, 3}
# Доступ по индексу (с 0)
iex> elem(tuple, 1)
2
# Размер кортежа
iex> tuple_size(tuple)
3
# Изменение элемента (создаёт новый кортеж!)
iex> put_elem(tuple, 0, :a)
{:a, 2, 3}
# Оригинальный кортеж не изменился
iex> tuple
{1, 2, 3}
Когда использовать кортежи:
- Фиксированное количество элементов
- Возврат значений из функций
- Pattern matching
Примеры использования:
☯
Terminal:
⌕
≡
✕
# Возврат статуса и значения
{:ok, result} = perform_operation()
# RGB цвета
color = {255, 128, 0}
# Координаты
point = {10, 20}
Lists (Списки)
Списки - это связанные списки (linked lists).
☯
Terminal:
⌕
≡
✕
# Создание списка
iex> [1, 2, 3]
[1, 2, 3]
iex> list = [1, 2, 3, 4, 5]
# Длина списка
iex> length(list)
5
# Конкатенация
iex> [1, 2] ++ [3, 4]
[1, 2, 3, 4]
# Вычитание
iex> [1, 2, 3, 4] -- [2, 4]
[1, 3]
# Голова и хвост
iex> hd([1, 2, 3])
1
iex> tl([1, 2, 3])
[2, 3]
# Добавление в начало (эффективная операция!)
iex> [0 | [1, 2, 3]]
[0, 1, 2, 3]
# Можно разделить список на голову и хвост
iex> [head | tail] = [1, 2, 3]
iex> head
1
iex> tail
[2, 3]
Важные особенности списков:
- Доступ к голове: O(1)
- Доступ по индексу: O(n)
- Добавление в начало: O(1)
- Добавление в конец: O(n)
☯
Terminal:
⌕
≡
✕
# Проверка на вхождение
iex> 2 in [1, 2, 3]
true
# Списки могут содержать разные типы
iex> [1, :atom, "string", 3.14]
[1, :atom, "string", 3.14]
# Списки списков
iex> 1, 2], [3, 4], [5, 6
1, 2], [3, 4], [5, 6
Keyword Lists (Списки ключевых слов)
Специальный синтаксис для списков кортежей с атомами.
☯
Terminal:
⌕
≡
✕
# Обычный синтаксис
iex> [{:a, 1}, {:b, 2}]
[a: 1, b: 2]
# Краткий синтаксис
iex> [a: 1, b: 2]
[a: 1, b: 2]
# Доступ к значениям
iex> list = [a: 1, b: 2]
iex> list[:a]
1
iex> list[:c]
nil
# Можно иметь дубликаты ключей
iex> [a: 1, a: 2]
[a: 1, a: 2]
Когда использовать keyword lists:
- Опции функций
- Запросы к базе данных
- Когда порядок важен
- Когда могут быть дубликаты ключей
☯
Terminal:
⌕
≡
✕
# Типичное использование
def render(template, opts \\ []) do
# opts может быть [title: "Page", layout: "main"]
end
# Вызов
render("index.html", title: "Home", layout: "app")
Maps (Словари)
Maps - это key-value структуры данных.
☯
Terminal:
⌕
≡
✕
# Создание map
iex> %{"a" => 1, "b" => 2}
%{"a" => 1, "b" => 2}
iex> %{:a => 1, :b => 2}
%{a: 1, b: 2}
# Короткий синтаксис для atom ключей
iex> %{a: 1, b: 2}
%{a: 1, b: 2}
# Map может иметь ключи любого типа
iex> %{1 => "one", 2 => "two"}
%{1 => "one", 2 => "two"}
# Доступ к значениям
iex> map = %{a: 1, b: 2}
iex> map[:a]
1
iex> map.a
1
iex> Map.get(map, :c, "default")
"default"
# Обновление map (создаёт новый!)
iex> %{map | a: 3}
%{a: 3, b: 2}
# Добавление нового ключа
iex> Map.put(map, :c, 3)
%{a: 1, b: 2, c: 3}
# Удаление ключа
iex> Map.delete(map, :a)
%{b: 2}
# Проверка наличия ключа
iex> Map.has_key?(map, :a)
true
# Получить все ключи
iex> Map.keys(map)
[:a, :b]
# Получить все значения
iex> Map.values(map)
[1, 2]
Важно: Ключи в maps уникальны, в отличие от keyword lists!
☯
Terminal:
⌕
≡
✕
# Вложенные maps
iex> user = %{
...> name: "John",
...> age: 30,
...> address: %{
...> city: "New York",
...> country: "USA"
...> }
...> }
# Доступ к вложенным значениям
iex> user.address.city
"New York"
# Обновление вложенных значений
iex> put_in(user.address.city, "Boston")
%{
name: "John",
age: 30,
address: %{city: "Boston", country: "USA"}
}
2.3 Концепция неизменяемости (Immutability)
Главный принцип: В Elixir все данные неизменяемы!
☯
Terminal:
⌕
≡
✕
# Пример
iex> list = [1, 2, 3]
iex> List.delete(list, 1)
[2, 3]
# Оригинальный список не изменился!
iex> list
[1, 2, 3]
Почему неизменяемость важна:
- Предсказуемость - данные не меняются неожиданно
- Безопасность в многопоточности - нет race conditions
- Легче тестировать - функции детерминированы
- Проще отладка - легче отследить flow данных
☯
Terminal:
⌕
≡
✕
# Чтобы "изменить" данные, нужно пересвязать переменную
iex> list = [1, 2, 3]
iex> list = [0 | list]
iex> list
[0, 1, 2, 3]
Структурное разделение (Structural Sharing):
Elixir оптимизирует память, переиспользуя неизменённые части структур данных.
☯
Terminal:
⌕
≡
✕
# При добавлении в начало списка
# новый список разделяет память с оригинальным
iex> original = [1, 2, 3]
iex> new_list = [0 | original]
# В памяти: [0] -> [1, 2, 3]
# ↑ ↑
# new_list original
2.4 Функции для работы с типами
Проверка типов
☯
Terminal:
⌕
≡
✕
iex> is_atom(:hello)
true
iex> is_binary("hello")
true
iex> is_boolean(true)
true
iex> is_float(3.14)
true
iex> is_integer(42)
true
iex> is_list([1, 2, 3])
true
iex> is_map(%{a: 1})
true
iex> is_tuple({1, 2, 3})
true
iex> is_number(42)
true
iex> is_number(3.14)
true
Преобразование типов
☯
Terminal:
⌕
≡
✕
# String to Integer
iex> String.to_integer("123")
123
# String to Float
iex> String.to_float("3.14")
3.14
# Integer to String
iex> Integer.to_string(123)
"123"
# Float to String
iex> Float.to_string(3.14)
"3.14"
# Atom to String
iex> Atom.to_string(:hello)
"hello"
# String to Atom
iex> String.to_atom("hello")
:hello
# Tuple to List
iex> Tuple.to_list({1, 2, 3})
[1, 2, 3]
# List to Tuple
iex> List.to_tuple([1, 2, 3])
{1, 2, 3}
Практическое задание 2.1
Создайте файл lib/data_types_practice.ex:
☯
Terminal:
⌕
≡
✕
defmodule DataTypesPractice do
@moduledoc """
Практика работы с типами данных
"""
# 1. Функция для работы со строками
def format_name(first_name, last_name) do
# Объедините имя и фамилию, сделайте первые буквы заглавными
# Подсказка: используйте String.capitalize/1
end
# 2. Функция для работы со списками
def sum_list(list) do
# Напишите функцию, которая суммирует все элементы списка
# Подсказка: используйте рекурсию или Enum.sum/1
end
# 3. Функция для работы с maps
def get_user_info(user) do
# user - это map с ключами :name, :age, :email
# Верните строку вида: "Name: John, Age: 30, Email: john@example.com"
end
# 4. Функция для работы с кортежами
def swap_tuple({a, b}) do
# Поменяйте местами элементы кортежа
end
# 5. Функция для работы с keyword lists
def create_options(title, show_border \\ true, color \\ :blue) do
# Верните keyword list с опциями
end
end
Решение практического задания 2.1
☯
Terminal:
⌕
≡
✕
defmodule DataTypesPractice do
@moduledoc """
Практика работы с типами данных
"""
def format_name(first_name, last_name) do
capitalized_first = String.capitalize(first_name)
capitalized_last = String.capitalize(last_name)
"#{capitalized_first} #{capitalized_last}"
end
def sum_list([]), do: 0
def sum_list([head | tail]) do
head + sum_list(tail)
end
# Или проще:
# def sum_list(list), do: Enum.sum(list)
def get_user_info(user) do
"Name: #{user.name}, Age: #{user.age}, Email: #{user.email}"
end
def swap_tuple({a, b}) do
{b, a}
end
def create_options(title, show_border \\ true, color \\ :blue) do
[title: title, show_border: show_border, color: color]
end
end
Тестирование (test/data_types_practice_test.exs)
☯
Terminal:
⌕
≡
✕
defmodule DataTypesPracticeTest do
use ExUnit.Case
alias DataTypesPractice, as: DTP
test "format_name/2 capitalizes names" do
assert DTP.format_name("john", "doe") == "John Doe"
assert DTP.format_name("MARY", "smith") == "Mary Smith"
end
test "sum_list/1 sums all elements" do
assert DTP.sum_list([1, 2, 3, 4, 5]) == 15
assert DTP.sum_list([]) == 0
assert DTP.sum_list([10]) == 10
end
test "get_user_info/1 formats user information" do
user = %{name: "John", age: 30, email: "john@example.com"}
expected = "Name: John, Age: 30, Email: john@example.com"
assert DTP.get_user_info(user) == expected
end
test "swap_tuple/1 swaps tuple elements" do
assert DTP.swap_tuple({1, 2}) == {2, 1}
assert DTP.swap_tuple({:a, :b}) == {:b, :a}
end
test "create_options/3 creates keyword list" do
result = DTP.create_options("My Title")
assert result == [title: "My Title", show_border: true, color: :blue]
result = DTP.create_options("Test", false, :red)
assert result == [title: "Test", show_border: false, color: :red]
end
end
Практическое задание 2.2 (Дополнительное)
Создайте модуль для работы с корзиной покупок:
☯
Terminal:
⌕
≡
✕
defmodule ShoppingCart do
@moduledoc """
Модуль для управления корзиной покупок
"""
# Корзина представлена как map:
# %{
# items: [%{name: "Apple", price: 1.50, quantity: 3}],
# total: 4.50
# }
def new do
# Создать новую пустую корзину
end
def add_item(cart, name, price, quantity \\ 1) do
# Добавить товар в корзину
end
def remove_item(cart, name) do
# Удалить товар из корзины
end
def calculate_total(cart) do
# Пересчитать общую стоимость
end
def item_count(cart) do
# Вернуть общее количество товаров
end
end
Резюме Темы 2
Ключевые понятия:
✅ Базовые типы:
- Integer, Float
- Boolean (только
falseиnil- falsy) - Atom - константы (
:ok,:error) - String - UTF-8 binaries
✅ Составные типы:
- Tuple - фиксированный размер, быстрый доступ
- List - динамический размер, linked list
- Keyword List - для опций функций
- Map - key-value структура
✅ Неизменяемость:
- Все данные immutable
- Изменения создают новые структуры
- Structural sharing для эффективности
✅ Функции проверки и преобразования типов
Следующий шаг: Тема 3 - Pattern matching и функции