Статьи

Эрланг и Эликсир, часть 2: типы данных

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]

Списки ключевых слов имеют три важных момента для запоминания:

  1. Ключи должны быть atoms .
  2. Ключи заказываются, как указано разработчиком.
  3. Ключи можно давать более одного раза.

Для запросов к базе данных 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

Они похожи на списки ключевых слов, но не идентичны — есть два различия:

  1. Карты допускают любое значение в качестве ключа.
  2. Ключи карт не следуют никакому порядку.

Примечание. Когда вы задаете все ключи в 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

Бывает, что мы также можем использовать этот же синтаксис для обновления значения:

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 и базовыми данными — это лишь малая часть всего спектра, предлагаемого разработчикам. Мы рассмотрим все это более подробно, поскольку мы продолжим в следующих частях серии.