В этой главе мы узнаем больше о базовых типах Эликсира: целых числах, числах с плавающей точкой, логических или булевых значениях, атомах, строках, списках и кортежах. Вот некоторые базовые типы:
iex> 1 # Целое число
iex> 0x1F # Целое число
iex> 1.0 # Число с плавающей точкой
iex> true # Логическое значение
iex> :atom # Атом
iex> "elixir" # Строка
iex> [1, 2, 3] # Список
iex> {1, 2, 3} # Кортеж
Базовая арифметика
Откройте IEx
и введите следующие выражения:
iex> 1 + 2
3
iex> 5 * 5
25
iex> 10 / 2
5.0
Обратите внимание, что выражение 10 / 2
вернёт число с плавающей точкой 5.0
, а не целое 5
. Это ожидаемо. В Эликсире оператор /
всегда возвращает float
. Если вы хотите совершить целочисленное деление или получить остаток от деления, вы можете использовать функции div
и rem
(от англ. division и remainder):
iex> div(10, 2)
5
iex> div 10, 2
5
iex> rem 10, 3
1
Эликсир позволяет опустить скобки при вызове именованных функций. Такая возможность позволяет получить более читаемый синтаксис при написании декларативных спецификаций и конструкций управления потоком.
Эликсир также поддерживает краткую форму написания двоичных, восьмиричных и шестнадцатеричных чисел:
iex> 0b1010
10
iex> 0o777
511
iex> 0x1F
31
Числа с плавающей запятой обязательно должны содержать точку и хотя бы одну цифру после неё, а также поддерживают e
-нотацию для экспоненциальных чисел:
iex> 1.0
1.0
iex> 1.0e-10
1.0e-10
Числа с плавающей точкой в Эликсире занимают 64 бита и имеют двойную точность.
Вы можете выполнить функцию round
, чтобы получить ближайшее целое число к аргументу с типом float
, или функцию trunc
, которая вернёт целую часть от числа.
iex> round(3.58)
4
iex> trunc(3.58)
3
Идентификация функций
Функции в Эликсире идентифицируются по их именам и их арности. Арность функции – это количество аргументов, которые принимает функция. С этого момента мы будем использовать как имя функции, так и её арность для обозначения функции во всей документации. Обозначение round/1
обозначает функцию с именем round
, которая принимает один аргумент, в то время как round/2
обозначает другую (несуществующую) функцию с тем же именем, но с арностью два.
Логические значения
Эликсир поддерживает true
и false
в качестве логических значений:
iex> true
true
iex> true == false
false
Эликсир предоставляет набор функций для проверки типа. Например, функция is_boolean/1
может быть использована для проверки, является значение логическим или нет:
iex> is_boolean(true)
true
iex> is_boolean(1)
false
Вы можете также использовать функции is_integer/1
, is_float/1
или is_number/1
, чтобы проверить, является ли аргумент целым числом, дробным числом или одним из них.
Помните. В любой момент вы можете набрать
h()
в интерактивной оболочке и получить информацию, как её использовать. Хелперh
может также быть использован для доступа к документации по любой функции. Например, командаh is_integer/1
напечатает документацию функцииis_integer/1
. Это также работает для операторов и других конструкций (попробуйтеh ==/2
).
Атомы
Атомы – это константы, имя которых является также и их значением. Некоторые другие языки называют их символами:
iex> :hello
:hello
iex> :hello == :world
false
Логические значения true
и false
, фактически, являются атомами:
iex> true == :true
true
iex> is_atom(false)
true
iex> is_boolean(:false)
true
Строки
Строки в Эликсире располагаются внутри двойных кавычек, и они представлены в кодировке UTF-8
:
iex> "hellö"
"hellö"
Обратите внимание. Если вы работаете под Виндоус, ваш терминал может использовать отличную от
UTF-8
кодировку по умолчанию. Вы можете сменить кодировку текущей сессии командойchcp 65001
перед запускомIEx
.
Эликсир также поддерживает интерполяцию строк:
iex> "hellö #{:world}"
"hellö world"
Строки могут содержать разрывы. Вы можете ввести их, используя экранированные последовательности:
iex> "hello
...> world"
"hello\nworld"
iex> "hello\nworld"
"hello\nworld"
Вы можете вывести строку, используя функцию IO.puts/1
из модуля IO
:
iex> IO.puts "hello\nworld"
hello
world
:ok
Помните, что функция IO.puts/1
возвращает атом :ok
в качестве результата после вывода.
Строки в Эликсире представлены внутри двоичными данными, которые являются последовательностями байтов:
iex> is_binary("hellö")
true
Мы также можем получить количество байт в строке:
iex> byte_size("hellö")
6
Обратите внимание, что количество байт в данной строке равно 6, хотя в ней 5 символов. Дело в том, что символ «ö» занимает 2 байта, чтобы быть представленным в UTF-8
. Мы можем получить реальную длину строки, основанную на количестве символов, использовав функцию String.length/1
:
iex> String.length("hellö")
5
Модуль String
содержит набор функций для манипуляций со строками, удовлетворяющий стандарту Юникод:
iex> String.upcase("hellö")
"HELLÖ"
Анонимные функции
Анонимные функции могут быть созданы в одну строку. Они заключаются между ключевыми словами fn
и end
:
iex> add = fn a, b -> a + b end
#Function<12.71889879/2 in :erl_eval.expr/5>
iex> add.(1, 2)
3
iex> is_function(add)
true
iex> is_function(add, 2) # проверяет, что `add` – функция, принимающая ровно два аргумента
true
iex> is_function(add, 1) # проверяет, что `add` – функция, принимающая ровно один аргумент
false
Функции в Эликсире являются сущностями первого класса, то есть они могут быть переданы в качестве аргумента другой функции, так же, как целые числа и строки. В примере мы передали функции is_function/1
переменную add
и она вернула true
. Мы можем также проверить арность функции вызовом is_function/2
.
Обратите внимание, что точка .
между переменной и скобками обязательна для вызова анонимной функции. Эта точка нужна, чтобы избавиться от неоднозначности между анонимной функцией add
и именованной add/2
. В этом смысле в Эликсире явно различаются анонимные и именованные функции. Мы поговорим об этой разнице в главе «Модули и функции».
Анонимные функции являются замыканиями и по существу они могут иметь доступ к переменным из области видимости, когда функция объявлена. Давайте объявим новую анонимную функцию, использующую анонимную функцию add
, которую мы объявили ранее:
iex> double = fn a -> add.(a, a) end
#Function<6.71889879/1 in :erl_eval.expr/5>
iex> double.(2)
4
Помните, что присвоение значений внутри функции никак не изменяют её окружение:
iex> x = 42
42
iex> (fn -> x = 0 end).()
0
iex> x
42
(Связные) Списки
Эликсир использует квадратные скобки, чтобы задать список значений. Значения могут быть любого типа:
iex> [1, 2, true, 3]
[1, 2, true, 3]
iex> length [1, 2, 3]
3
Два списка могут быть сложены, используя оператор ++/2
, и один может быть вычтен из другого с помощью --/2
:
iex> [1, 2, 3] ++ [4, 5, 6]
[1, 2, 3, 4, 5, 6]
iex> [1, true, 2, false, 3, true] -- [true, false]
[1, 2, 3, true]
В этом руководстве мы будем много говорить о голове и хвосте списка. Головой называют первый элемент списка, а хвостом – его оставшуюся часть. Они могут быть получены функциями hd/1
и tl/1
. Давайте создадим список и получим его голову и хвост:
iex> list = [1, 2, 3]
iex> hd(list)
1
iex> tl(list)
[2, 3]
Попытка получить голову или хвост пустого списка выбросит ошибку:
iex> hd []
** (ArgumentError) argument error
Иногда вы будете создавать список и он будет возвращать значение в одинарных кавычках. Например:
iex> [11, 12, 13]
'\v\f\r'
iex> [104, 101, 108, 108, 111]
'hello'
Когда Эликсир видит список корректных ASCII
-кодов, которые может напечатать, он выводит список символов (в буквальном смысле). Списки символов часто используются для взаимодействия с существующим кодом на Эрланге. Если вы видите значение в IEx
, и вы не уверены, что это такое, вы можете использовать i/1
для получения информации:
iex> i 'hello'
Term
'hello'
Data type
List
Description
...
Raw representation
[104, 101, 108, 108, 111]
Reference modules
List
Помните, что значения в одинарных и двойных кавычках не эквивалентны в Эликсире, они принадлежат разным типам:
iex> 'hello' == "hello"
false
Внутри одиночных кавычек списки символов, внутри двойных – строки. Мы поговорим об этом больше в главе «Двоичные данные, строки и списки символов».
Кортежи
Эликсир использует фигурные скобки для объявления кортежей. Как и списки, кортежи могут хранить любые значения:
iex> {:ok, "hello"}
{:ok, "hello"}
iex> tuple_size {:ok, "hello"}
2
Кортежи хранят элементы в памяти последовательно. Это значит, что доступ к элементу кортежа по индексу или получение размера кортежа – быстрая операция. Индексы начинаются с нуля:
iex> tuple = {:ok, "hello"}
{:ok, "hello"}
iex> elem(tuple, 1)
"hello"
iex> tuple_size(tuple)
2
Также можно добавить элемент на определённое место в кортеже с помощью функции put_elem/3
:
iex> tuple = {:ok, "hello"}
{:ok, "hello"}
iex> put_elem(tuple, 1, "world")
{:ok, "world"}
iex> tuple
{:ok, "hello"}
Обратите внимание, что функция put_elem/3
вернёт новый кортеж. Исходный кортеж, который хранится в переменной tuple
, не изменяется, потому что данные в Эликсире иммутабельны. Благодаря этому, вам не придётся беспокоиться, что какой-то код может изменить ваши структуры данных.
Списки или кортежи?
Какая разница между списками и кортежами?
Списки хранятся в памяти, как связные списки, это значит, что каждый элемент списка хранит своё значение и указывает на следующий элемент, пока не будет достигнут конец списка.
iex> list = [1 | [2 | [3 | []]]]
[1, 2, 3]
Это значит, что получение длины списка – линейная операция: нам нужно пройти через весь список последовательно, чтобы узнать его размер. Обновление списка – быстрая операция, также как добавление элементов в начало:
iex> [0 | list]
[0, 1, 2, 3]
Кортежи, с другой стороны, хранятся в памяти последовательно. Это значит, что получение размера кортежа или доступ к элементу по индексу работают быстро. Однако, обновление или добавление элементов в кортеже – дорогая операция, поскольку включает в себя копирование всего кортежа в память.
Использование той или иной структуры данных продиктовано особенностями их производительности. Наиболее частый случай использования кортежей – возврат дополнительной информации из функции. Например, File.read/1
– функция, которая используется для чтения содержимого файлов. Она возвращает кортежи:
iex> File.read("path/to/existing/file")
{:ok, "... contents ..."}
iex> File.read("path/to/unknown/file")
{:error, :enoent}
Если путь, переданный в функцию File.read/1
, существует, она возвращает кортеж с атомом :ok
в качестве первого элемента и содержимым файла в качестве второго. Иначе, она возвращает кортеж с :error
и описанием ошибки.
Большую часть времени Эликсир пытается направить вас поступать правильно. Например, есть функция elem/2
для доступа к элементу кортежа, но нет встроенного эквивалента этой функции для списков:
iex> tuple = {:ok, "hello"}
{:ok, "hello"}
iex> elem(tuple, 1)
"hello"
При подсчёте элементов в структурах данных, Эликсир также придерживается простого правила: функция называется size
, если операция выполняется за константное время (значение предварительно посчитано), или length
, если операция линейна (подсчёт длины тем дольше, чем больше переданное значение). Удобно запомнить, что «length» и «linear» оба начинаются с «l».
Например, мы уже использовали 4 функции подсчёта: byte_size/1
(количество байт, занимаемых строкой), tuple_size/1
(размер кортежа), length/1
(длина списка), String.length/1
(количество графем в строке). Мы использовали byte_size
для получения количества байт, которые занимает строка – это дешёвая операция. Для получения количества символов Юникода, с другой стороны, используется String.length
, что может быть дорого, т. к. для этого нужен обход всей строки.
Эликсир также предоставляет модули Port
, Reference
, и PID
как типы данных (обычно используются при общении процессов), и мы уделим им немного внимания, когда будем говорить о процессах. Теперь давайте познакомимся с базовыми операторам, которые используются с нашими базовыми типами.