Elixir предлагает множество типов данных. Здесь представлены обычные базовые типы integer
, float
, boolean
и string
, но также atom
/ символ, list
, tuple
и anonymous functions
. Вы узнаете все о них в этом уроке.
Прежде чем мы начнем: мы будем запускать эти примеры в интерактивном режиме Elixir . Введите iex
в свой терминал, чтобы войти в интерактивный режим. Для получения дополнительной информации прочитайте Часть 1 этого руководства. (Пользователи Windows запускают iex.bat --werl
.)
Предисловие
Эликсир создан с учетом метапрограммирования. Все это построено на макросах . Если вы не знакомы с макросами, это просто отдельная инструкция, выполняющая определенную задачу . Не очень сложно разобраться, но вот несколько реальных примеров:
1
2
3
4
5
6
7
|
if worked?
IO.puts(«You just used a macro»)
end
if :name?
IO.puts(:name)
end
|
if
здесь макрос для стандартной условной структуры if … мы все знаем. Эликсир скомпилирует результат из макроса для вас внутри.
Мета-программирование?
Основой Elixir является манипулирование выражениями в кавычках .
Мета-программирование, по сути, означает, что вы можете создавать код из кода (программы имеют возможность обрабатывать свой собственный код как данные по сути … действительно, огромный шаг)
Программа Elixir может быть представлена в виде собственной структуры данных.
Например, строительные блоки Elixir представлены tuple
из трех элементов (подробнее о tuples
позже). Сумма вызова функции sum(1,2,3)
определяется так:
1
|
{:sum, [], [1, 2, 3]}
|
Вы можете получить представление любого макроса с помощью макроса quote
:
1
2
|
iex> quote do: sum(1, 2, 3)
{:sum, [], [1, 2, 3]}
|
Примечание. В дополнение к макросу quote
макрос unquote
— вы можете прочитать больше об этом в документации по Elixir .
Итак, мы видим, что первый элемент — это имя функции :sum
, второй — keyword list
(подробнее об этом позже), который содержит метаданные (в настоящее время пустые в примере), а третий — список аргументов.
Все макросы создаются с использованием этих структур данных, поэтому это означает, что наш код может иметь врожденную способность изменять и перекомпилировать свой собственный код. Таким образом, ваше приложение может теперь написать свой собственный код, проверять наличие ошибок в коде и исправлять их, или даже сканировать добавляемый плагин, проверять код внутри и изменять его на лету.
Последствия для искусственного интеллекта , очевидно, огромны, но сначала, в этом руководстве, мы должны продолжить с основ и получить прочную основу для различных типов данных, используемых Elixir, прежде чем мы начнем углубляться в глубины.
Основные типы
Теперь, когда вы находитесь в интерактивной консоли, давайте посмотрим на Booleans
.
1
2
3
4
|
iex> true
true
iex> true == false
false
|
Поддерживается стандартное поведение true
и false
. Чтобы проверить значение, мы используем функцию, предоставляемую Elixir, is_boolean
.
1
2
3
4
|
iex> is_boolean(true)
true
iex> is_boolean(1)
false
|
Каждый тип также имеет одну из этих функций предиката. Например, is_integer/1
, is_float/1
или is_number/1
все проверит, является ли аргумент целым числом, числом с плавающей запятой или любым из них.
Elixir всегда ссылается на функции таким образом, с косой чертой, за которой следует число, чтобы обозначить количество аргументов, которые принимает функция. Поэтому для is_boolean/1
требуется 1 аргумент, например, is_boolean(1)
.
Примечание . Если вы хотите в любое время получить доступ к справке в интерактивной оболочке, просто введите h
и вы сможете получить доступ к информации о том, как использовать оболочку Elixir. Кроме того, вы можете найти информацию о любом из операторов или функций Elixir, используя h is_integer/1
для получения документации по is_integer/1
.
Математически мы также можем выполнять такие функции:
1
2
3
4
|
iex> round(3.58)
4
iex> trunc(3.58)
3
|
В Elixir, когда деление выполняется на integer
, возвращаемое значение всегда является float
:
1
2
3
4
|
iex> 5 / 2
2.5
iex> 10 / 2
5.0
|
Если мы хотим получить остаток от деления или сделать целочисленное деление, мы можем использовать функции div/2
и rem/2
следующим образом:
1
2
3
4
5
6
|
iex> div(10, 2)
5
iex> div 10, 2
5
iex> rem 10, 3
1
|
Примечание. Скобки не требуются для вызовов функций.
Вы также можете просто ввести любые binary
, octal
или hexadecimal
числа в iex
.
1
2
3
4
5
6
|
iex> 0b1010
10
iex> 0o777
511
iex> 0x1F
31
|
Атомы (символы)
Это как constants
, но их имя — их собственная ценность. Некоторые языки называют это символами. Логические значения true
и false
также являются примерами символов.
1
2
3
4
|
iex> :hello
:hello
iex> :hello == :world
false
|
Кортеж
Подобно lists
и определенным фигурными скобками, они могут содержать любые данные, например:
1
2
3
4
|
iex> {:ok, «hello»}
{:ok, «hello»}
iex> tuple_size {:ok, «hello»}
2
|
Списки
Подобно tuples
, вы определяете list
квадратных скобках следующим образом:
1
2
3
4
|
iex> [1, 2, true, 3]
[1, 2, true, 3]
iex> length [1, 2, 3]
3
|
Два списка могут быть объединены и вычтены также:
1
2
3
4
|
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
и tl
(сокращение от head и tail).
В чем разница между списками и кортежами?
Lists
хранятся в памяти как связанные списки, список пар значений, который повторяется и доступен в линейной операции. Таким образом, обновление выполняется быстро, пока мы добавляем в list
, но если мы вносим изменения в list
, операция будет медленнее.
Tuples
, с другой стороны, хранятся в памяти непрерывно. Это означает, что получить общий размер кортежа или получить доступ только к одному элементу очень быстро. Но здесь, по сравнению со списками, добавление к существующему tuple
является медленным и требует внутреннего копирования всего tuple
в памяти.
Анонимные функции
Мы можем определить функцию, используя ключевые слова fn
и end
.
01
02
03
04
05
06
07
08
09
10
|
iex> myFunc = fn a, b -> a + b end
#Function<12.71889879/2 in :erl_eval.expr/5>
iex> is_function(myFunc)
true
iex> is_function(myFunc, 2)
true
iex> is_function(myFunc, 1)
false
iex> myFunc.(1, 2)
3
|
В руководстве Elixir анонимные функции называются «гражданами первого класса». Это означает, что вы можете передавать аргументы другим функциям так же, как это могут делать целые числа или строки.
Итак, в нашем примере мы объявили анонимную функцию в переменной myFunc
для функции проверки is_function(myFunc)
, которая правильно вернула true
. Это означает, что мы успешно создали нашу функцию.
Мы также можем проверить количество аргументов нашей функции myFunc
, вызвав is_function(myFunc, 2)
.
Примечание. Использование точки ( .
) Между переменной и круглыми скобками необходимо для вызова анонимной функции.
Струны
Мы определяем строки в эликсире между двойными кавычками:
1
2
|
iex> «yo»
«yo»
|
Интерполяция поддерживается также знаком #
:
1
2
|
iex> «Mr #{:Smith}»
«Mr Smith»
|
Для объединения используйте <>
.
1
2
|
iex> «foo» <> «bar»
«foobar»
|
Чтобы получить длину строки, используйте функцию String.length
:
1
2
|
iex> String.length(«yo»)
2
|
Чтобы разделить строку на основе шаблона, вы можете использовать метод String.split
:
1
2
3
4
5
|
iex> String.split(«foo bar», » «)
[«foo», «bar»]
iex> String.split(«foo bar!», [» «, «!»])
[«foo», «bar», «»]
|
Списки ключевых слов
Обычно используемый в программировании, пара ключ-значение из двух записей данных, по сути, двух tuples
элементов, может быть создана так, где ключ является atom
:
1
2
3
4
|
iex> list = [{:a, 1}, {:b, 2}]
[a: 1, b: 2]
iex> list == [a: 1, b: 2]
true
|
Elixir имеет синтаксис для определения списков как [key: value]
. Затем мы можем использовать любые другие операторы, доступные в Elixir, такие как ++
для добавления в начало или конец списка.
1
2
3
4
|
iex> list ++ [c: 3]
[a: 1, b: 2, c: 3]
iex> [a: 0] ++ list
[a: 0, a: 1, b: 2]
|
Списки ключевых слов имеют три важных момента для запоминания:
- Ключи должны быть
atoms
. - Ключи заказываются, как указано разработчиком.
- Ключи можно давать более одного раза.
Для запросов к базе данных Ecto Library использует это при выполнении запроса следующим образом:
1
2
3
4
|
query = from w in Weather,
where: w.prcp > 0,
where: w.temp < 20,
select: w
|
Макрос Elixir if
также включает это в свой дизайн:
1
2
|
iex> if(false, [{:do, :this}, {:else, :that}])
:that
|
В общем случае, когда список является аргументом функции, квадратные скобки являются необязательными.
Карты
Как и в списках ключевых слов, существуют maps
которые помогут удовлетворить ваши потребности пары ключ-значение. Они создаются через синтаксис %{}
следующим образом:
1
2
3
4
5
6
7
8
|
iex> map = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> map[:a]
1
iex> map[2]
:b
iex> map[:c]
nil
|
Они похожи на списки ключевых слов, но не идентичны — есть два различия:
- Карты допускают любое значение в качестве ключа.
- Ключи карт не следуют никакому порядку.
Примечание. Когда вы задаете все ключи в map
как atoms
, синтаксис ключевого слова может быть очень удобным:
1
2
|
iex> map = %{a: 1, b: 2}
%{a: 1, b: 2}
|
согласование
Карты очень хороши для сопоставления с образцом, в отличие от списков ключевых слов. Когда map
используется в шаблоне, она всегда будет соответствовать в подмножестве заданного значения.
1
2
3
4
5
6
7
8
|
iex> %{} = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> %{:a => a} = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> a
1
iex> %{:c => c} = %{:a => 1, 2 => :b}
** (MatchError) no match of right hand side value: %{2 => :b, :a => 1}
|
Шаблон всегда будет совпадать, пока ключи существуют на предоставленной карте. Таким образом, чтобы соответствовать всем, мы используем пустую карту.
Чтобы продвигать карты дальше, модуль Map предоставляет мощный API для управления картами:
1
2
3
4
|
iex> Map.get(%{:a => 1, 2 => :b}, :a)
1
iex> Map.to_list(%{:a => 1, 2 => :b})
[{2, :b}, {:a, 1}]
|
Вложенные структуры данных
Данные часто требуют иерархий и структурирования. Elixir обеспечивает поддержку maps
внутри maps
или keyword lists
внутри maps
и так далее. put_in
этим удобно с помощью макросов put_in
и update_in
, которые можно использовать следующим образом:
Допустим, у нас есть следующее:
1
2
3
4
5
6
|
iex> users = [
john: %{name: «John», age: 27, languages: [«Erlang», «Ruby», «Elixir»]},
mary: %{name: «Mary», age: 29, languages: [«Elixir», «F#», «Clojure»]}
]
[john: %{age: 27, languages: [«Erlang», «Ruby», «Elixir»], name: «John»},
mary: %{age: 29, languages: [«Elixir», «F#», «Clojure»], name: «Mary»}]
|
Итак, теперь у нас есть некоторые данные для игры, и у каждого списка ключевых слов есть карта, содержащая имя, возраст и некоторую другую информацию. Если мы хотим узнать возраст Джона, мы можем сделать следующее:
1
|
iex> users[:john].age 27
|
Бывает, что мы также можем использовать этот же синтаксис для обновления значения:
iex> users = put_in users[:john].age, 31 [john: %{age: 31, languages: ["Erlang", "Ruby", "Elixir"], name: "John"}, mary: %{age: 29, languages: ["Elixir", "F#", "Clojure"], name: "Mary"}]
RegEx
Elixir использует модуль Erlang :re
, основанный на PCRE. Вы можете найти больше информации в документации .
Чтобы создать регулярное выражение в Elixir, используйте метод Regex.compile
или специальные сокращенные формы ~r
или ~R
1
2
3
4
5
|
# A simple regular expression that matches foo anywhere in the string
~r/foo/
# A regular expression with case insensitive and Unicode options
~r/foo/iu
|
Модуль Regex имеет множество полезных функций, которые вы можете использовать для проверки выражений регулярных выражений, их компиляции и корректного экранирования. Вот некоторые примеры:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
iex> Regex.compile(«foo»)
{:ok, ~r»foo»}
iex> Regex.compile(«*foo»)
{:error, {‘nothing to repeat’, 0}}
iex> Regex.escape(«.»)
«\\.»
iex> Regex.escape(«\\what if»)
«\\\\what\\ if»
iex> Regex.replace(~r/d/, «abc», «d»)
«abc»
iex> Regex.replace(~r/b/, «abc», «d»)
«adc»
iex> Regex.replace(~r/b/, «abc», «[\\0]»)
«a[b]c»
iex> Regex.match?(~r/foo/, «foo»)
true
|
Вы также можете запустить регулярное выражение на map
и получить результаты с named_captures
метода named_captures
:
1
2
3
4
5
6
7
8
|
iex> Regex.named_captures(~r/c(?<foo>d)/, «abcd»)
%{«foo» => «d»}
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/, «abcd»)
%{«bar» => «d», «foo» => «b»}
iex> Regex.named_captures(~r/a(?<foo>b)c(?<bar>d)/, «efgh»)
nil
|
Вывод
Elixir — полнофункциональная среда метапрограммирования, основанная на использовании макросов. Он может помочь вам как разработчику выйти на новый уровень разработки приложений и структурирования данных благодаря своим мощным lists
, maps
и anonymous functions
при использовании в сочетании с его возможностями метапрограммирования.
Мы можем взглянуть на конкретные решения в наших подходах, которые для создания не-макроязыков могли занять много строк или классов кода из-за мощного богатства, предлагаемого Elixir в форме принятия DSL (Domain Specific Language) и Модули Эрланга.
Манипуляции с хранилищем значений пары ключей, list
и базовыми данными — это лишь малая часть всего спектра, предлагаемого разработчикам. Мы рассмотрим все это более подробно, поскольку мы продолжим в следующих частях серии.