- PVSM.RU - https://www.pvsm.ru -

Elixir

Здравствуйте, сегодня я Вам расскажу о современном языке программирования под BeamVM (или ErlangVM).
Первая часть является неполным введением в основы, а вторая часть статьи показывает на простых примерах главные особенности языка, новые для эрланг разработчика.

Два года назад вышла 0.1 версия эликсира, которая и была представлена во внимание [1] хабрасообществу раньше.

Цитата:

«Erlang является уникальной по своим возможностям платформой, и не смотря на это, язык до сих пор является экзотикой. Причин существует несколько. Например, тугая арифметика, непривычность синтаксиса, функциональность. Это не недостатки. Это просто вещи, с которыми большинство программистов не могут или не хотят работать.»

На данный момент, Эликсир стал самым популярный языком программирования(естественно, помимо эрланга) построенного поверх BeamVM. Вплоть до того, что автор эрланга Joe Armstrong посвятил статью, а Dave Thomas написал книгу. За два года очень многое изменилось, язык сильно стабилизировался и обрёл более или менее конечный вариант для версии 1.0. За это время, из эликсира исчезла объектная модель, остался Ruby-подобный синтаксис, но добавился метапрограмминг и полиморфизм, которые аргонично, в отличие от объектноориентированной парадигмы вписываются в Beam VM.

Новое в Elixir-е:

  • Ruby-подобный синтакс(семантика не как в Ruby)
  • Полиморфизм с помощью протоколов
  • Метапрограммирование
  • Стандартизированная библиотека
  • «First class shell»
  • И ещё много-много другого

При этом компилируется в beam-код эрланга, Elixir позволяет Вам вызывать модули Erlang без необходимости преобразовать типы данных, поэтому нет никакой потери в производительности при вызове кода Erlang.

Чтобы опробовать у себя, можно скачать его с гитхаба:

$ git clone https://github.com/elixir-lang/elixir.git
$ cd elixir
$ make test

Или установить предкомпилированную версию [2].

А так же для обладателей Fedora, Mac OS или Arch Linux можно установить эликсир через пакетменеджер:

  • Homebrew для Mac OS X:
    1. $ brew tap homebrew/versions
      $ brew install erlang-r16
      
    2. Если установлена предыдущая версия эрланга, то нужно link-овать новую версию эрланга:
      $ brew uninstall erlang
      $ brew link erlang-r16
      
    3. Установка эликсира:
      $ brew update
      $ brew install elixir
      

  • Fedora 17+ и Fedora Rawhide: sudo yum -y install elixir
  • Arch Linux: Elixir доступен через AUR: yaourt -S elixir

В эликсире имеется интерактивная консоль iex, в которой можно сразу же всё и попробовать. В отличие от эрланга в консоли эликсира можно создавать модули и кортежи, как будет показано ниже.

Комментарий:

# This is a commented line

Далее, “# =>” показывают значение выражения:

1 + 1 # => 2

Пример из консоли:

$ bin/iex

defmodule Hello do
 def world do
 IO.puts "Hello World"
 end
end
Hello.world

Типы данных в эликсире такие же, как и в эрланге:

1     # integer
0x1F  # integer
1.0   # float
:atom # atom / symbol
{1,2,3} # tuple
[1,2,3] # list
<<1,2,3>> # binary

Строки в эликсире, как и в эрланге могут быть представлены через листы или через binary:

'I am a list'
"I am a binary or a string"
name = "World"
"Hello, #{name}" # => string interpolation

В отличие от эрланга, эликсир использует везде binary, как стандартную имплементацию строк из-за скорости и компактности их перед листами.

A так же есть многострочные строки:

"""
This is a binary
spawning several
lines.
"""

Вызов функций, мы уже видели выше для модуля, но можно и так, опуская скобки:

div(10, 2)
div 10, 2

Хороший стиль программирования для эликсира рекомендует, если и опускать скобки, то при использовании макро.
Coding Style в стандартной библиотеке говорит о том, что для вызова функций скобки должны быть.

Переменные в эликсире являются mutable:

x = 1
x = 2

Но при этом сохранился весь pattern matching из эрланга и при этом можно с помощью ^ делать их неизменяемыми как в эрланге:

{a, b, [c | _]} = {1,2,["a", "b", "c"]} # => a = 1 b = 2 c = "a"

a = 1 # => 1
a = 2 # => 2
^a = 3 # => ** (MatchError) no match of right hand side value: 3

Подробнее ознакомиться с синтаксисом, возможностями и особенностями Эликсира можно здесь:
Оффициальный туториал [3]
Crash Курс для эрланг разработчиков [4]
Неделя с эликсиром. Статья Joe Armstrong об эликсире [5]
Книга Programming Elixir от Dave Thomas, там же есть два видеотуториала и несколько фрагментов из книги [6]
Оффициальная документация [7]

После того, как я сам начал программировать на эликсире, смотреть на код эрланга, который создаётся
часто через copy-paste с изменением одного значения(а такая необходимость есть почти в каждом проекте, который я встречал)
или постоянные паттерны повторения, которые увеличивают код, я не могу, мне так и хочется переписать их
грамотно на эликсире.

А сейчас хотелось бы показать на простых примерах нововведения для эрланг разработчика, a именно
метапрограммирование, полиморфизм, а так же синтактический сахар, которые сильно упрощают код.

Пример( «Everything is an expression» ):

defmodule Hello do
  def world do
    IO.puts "Hello World"
  end
end

Запищем его в фаил и скомпилируем его так:

$ elixirc hello.ex

Теперь, давайте изменим его немного:

defmodule Hello do

  IO.puts "Hello Compiler"
  def world do
    IO.puts "Hello World"
  end
end

Теперь попробуем два

defmodule Hello do
  if System.get_env("MY_ENV") == "1" do
    def world do
      IO.puts "Hello World"
    end
  else
    def world do
      IO.puts "Hello World with my Variable = 1"
    end
  end
end

А теперь, попробуем сгенерировать код.
В эрланг коде часто можно встретить такой или подобный код:

my_function(bad_type) -> 1;
my_function(bad_stat) -> 2;

.......
my_function(1) -> bad_type;
my_function(2) -> bad_stat;
.....

В эликсире, мы можем получить ту же скорость работы функции, не повторяясь, если будем генерировать те же самые функции вовремя компиляции:

list = [{:bad_type, 1}, {:bad_stat, 2}, ...]
Enum.map(list, fn({type, num}) ->
  def my_function(unquote(type)) do
    unquote(num)
  end
  def my_function(unquote(num)) do
    unquote(type)
  end
end)

Это взято из реального кода:
в одну сторону [8] и в другую сторону [9]
И в ту и в другую сторону, на эликсире [10]

Пример использования(с list compression, который в таких случаях используется намного чаще) в самом эликсире можно увидеть, например здесь:
github.com/elixir-lang/elixir/blob/master/lib/elixir/lib/string.ex#L478-L486 [11]

Макро в эликсире действуют, как в clojure(программисты лиспа будут чувствовать себя, как дома), у любого кода можно увидеть его AST:

quote do: 1+1 # => {:+,[context: Elixir, import: Kernel],[1,1]}
quote do: {1,2,3,4} # => {:"{}",[],[1,2,3,4]}
quote do: sum(1,2) # => {:sum,[],[1,2]}
quote do: sum(1, 2 + 3, 4) # => {:sum,[],[1,{:+,[context: Elixir, import: Kernel],[2,3]},4]}

Как видно из примеров AST состоит из кортежей с тремя элементами: {name, meta, arguments}
Теперь, попробуем написать наше первое макро:

defmodule MyMacro do
  defmacro unless(clause, options) do
    quote do: if(!unquote(clause), unquote(options))
  end
end

Теперь используем наше макро:

require MyMacro
MyMacro.unless 2 < 1, do: 1 + 2

Полиморфизм:

list = [{:a, 1}, {:b, 2}, {:c, 3}, {:d, 4}, {:e, 5}, {:f, 6}, {:g, 7}, {:h, 8}, {:k, 9}]

Enum.map(list, fn({a, x}) -> {a, x * 2} end)

dict = HashDict.New(list)

Enum.map(list, fn({a, x}) -> {a, x * 2} end)

file  = File.iterator!("README.md")
lines = Enum.map(file, fn(line) -> Regex.replace(%r/"/, line, "'") end)
File.write("README.md", lines)

Пример показывает, что мы можем использовать библиотеку Enum над любым типом данных, который имплементирует протокол Enumerable.

Имплементация для протоколоа может находиться где угодно, не зависимо от самого протокола, главное чтобы скомпилированный код находился там, где BeamVM может его найти(т.е. в :source.get_path). Т.е. например, Вы можете расширять существующие библиотеки, не изменяя их код для своих типов данных.

Ещё один интересный встроенный протокол — это access protocol, возьмём на примере верхнего листа:

list = [{:a, 1}, {:b, 2}, {:c, 3}, {:d, 4}, {:e, 5}, {:f, 6}, {:g, 7}, {:h, 8}, {:k, 9}]

list[:a] # => 1

Мы сделаем очень простой пример с бинарным деревом, который будет находиться в записи(record) Tree и для нашего дерева мы тоже имплементируем Access протокол.

defmodule TreeM do
  def has_value(nil, val), do: nil
  def has_value({{key, val}, _, _}, key), do: val
  def has_value({_, left, right}, key) do
    has_value(left, key) || has_value(right, key)
  end

end
defrecord Tree, first_node: nil

defimpl Access, for: Tree do
  def access(tree, key), do: TreeM.has_value(tree.first_node, key)
end

Теперь точно так же мы можем находить наши значения через Access Protocol

tree = Tree.new(first_node: {{:a, 1}, {{:b, 2}, nil, nil}, {{:c, 3}, nil,  nil}})
tree[:a] # => 1

Протоколы дают полиморфизм.

И теперь, немного синтактического сахара, который упрощает написание и чтение кода в определённых ситуациях.
[{:a, 1}] можно писать так: [a: 1]

Точно так же, часто приходиться писать такие конструкции, как:
func3(func2(func1(list))), не смотря на то что вызов функции func1 произойдёт первым, мы пишем вначале func3 или должны вводить переменные, как в этом случае:

file  = File.iterator!("README.md")
lines = Enum.map(file, fn(line) -> Regex.replace(%r/"/, line, "'") end)
File.write("README.md", lines)

C помощью «pipeline» оператора мы можем переписать наш пример так:

lines = File.iterator!("README.md") |> Enum.map(fn(line) -> Regex.replace(%r/"/, line, "'") end)
File.write("README.md", lines)

В библиотеке эликсира стандартизированно, субъект идёт первым аргументом. И это даёт возможность с помощью |> оператора, который подставляет результат предыдущего действия как первый аргумент функции в следующий вызов, писать более понятный, компактный и последовательный код.

Ещё, мы можем упростить этот пример, используя curry или partials в простых случаях:

lines = File.iterator!("README.md") |> Enum.map( Regex.replace(%r/"/, &1, "'") )
File.write("README.md", lines)

Я думаю, Elixir будет интересен и эрланг разработчикам, которые хотят улучшить качество своего кода, продуктивность, и опробовать метапрограмминг в действии, так же как и разработчкам с других языков и платформ, которые хотели бы опробовать BeamVM, но не решались из-за синтаксиса эрланга или сумбура в библиотеках, так как, например, ещё одно важное достоинство, Эликсир предоставляет стандартизированную, компактную и логичную стандартную библиотеку.

Автор: elimoon

Источник [12]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/erlang-otp/37111

Ссылки в тексте:

[1] во внимание: http://habrahabr.ru/post/115372/

[2] предкомпилированную версию: http://elixir-lang.org/packages.html

[3] Оффициальный туториал: http://elixir-lang.org/getting_started/1.html

[4] Crash Курс для эрланг разработчиков: http://elixir-lang.org/crash-course.html

[5] Неделя с эликсиром. Статья Joe Armstrong об эликсире: http://joearms.github.io/2013/05/31/a-week-with-elixir.html

[6] Книга Programming Elixir от Dave Thomas, там же есть два видеотуториала и несколько фрагментов из книги: http://pragprog.com/book/elixir/programming-elixir

[7] Оффициальная документация: http://elixir-lang.org/docs/

[8] в одну сторону: https://github.com/travelping/flower/blob/master/src/flower_packet.erl#L202-L220

[9] в другую сторону: https://github.com/travelping/flower/blob/master/src/flower_packet.erl#L239-L257

[10] И в ту и в другую сторону, на эликсире: https://gist.github.com/liveforeverx/5697501

[11] github.com/elixir-lang/elixir/blob/master/lib/elixir/lib/string.ex#L478-L486: https://github.com/elixir-lang/elixir/blob/master/lib/elixir/lib/string.ex#L478-L486

[12] Источник: http://habrahabr.ru/post/184222/