Защо Elixir?


Една тема е добре да започне с отговор на въпроса “Защо?”. Защо трябва да се интересуваме от нея? Защо ни е полезна. Въобще защо да се занимаваме с нея? Понякога отговорът е “просто за пробата”, понякога е “защото е нужен опит за бъдещо начинание”, понякога е нещо друго. Ние ще се опитаме да отговорим на въпроса “Защо Elixir”, за да не е “просто за пробата”. Може да успеем да ви убедим че е полезен инструмент или знание за вас, а може и да не успеем, всичко е инвидуално.

Като цяло дали да учим много програмни езици, дали да изберем обектно-ориентирания път или функционалния или пък да си пишем процедурно е дълбоко философска тема. Ще се докоснете леко до нея ако продължите да четете…

Нека започнем с TL;DR : click

Какво представлява Elixir?

Има някои факти за Elixir, които са в основата на потенциала му.

  • Езикът се ползва някъде от 2013 година, което го прави доста млад, тепърва има да набира популярност.
  • Създателят на Elixir, Жозе Валим (José Valim) идва от ruby/rails света. Това малко или много се е отразило на синтаксиса на езика.
  • Elixir върви на виртуалната машина на Erlang и може да ползва всичко написано на Erlang. Това се отразява добре на бързината и конкурентността му. Като цяло не е твърде далеч от истината да кажем че е по-човешки Erlang. Може да се интерпретира или компилира към BEAM код, който върви на виртуалната машина.
  • Подобно на Ruby си има web framework - Phoenix, съпоставим на Rails по начин на писане, но не и по производителност, в положителния смисъл.
  • Функционален език е. Това е голям плюс когато става въпрос за конкурентност. Също малко или много ще преобърне представите ви за програмиране.

В заключение - Elixir e среавнително нов функционален език, но е построен върху нещо изпитано и направено с цел да се справя добре в конкурентна среда.

Защо Elixir? Защото е функционален език!

Всъщност за някои хора това може и да е минус, за други трудност, за трети непознат термин. Elixir е доста различен от езици като Java или Ruby, макар и на пръв поглед да прилича на Ruby. Това е малко подвеждащо.

В Elixir функциите са основните градивни единици. Обикновено логиката представлява поредица от композирани функции. Нещо подобно на:

[1, 2, 3, 4, 5]
  |> Enum.map(fn n -> n * n end)
  |> Enum.filter(fn n -> n > 10 end)
  |> Enum.reduce(1, &(&1 + &2))

# -> 42

В примера виждаме списък, както и класическите функции от по-висок ред за работа със списъци - map, filter и reduce, както и pop-culture reference…

Синтаксисът по-горе може да изглежда доста странен, но всяко ново нещо изглежда странно преди да бъде опознато. Това че езикът е функционален, означава че ще си имаме работа с:

  • Непроменими (immutable) структури от данни - това означава, че ако искаме някаква промяна в дадената структура, правим нова базирана на старата, с разлика - тази промяна.
  • Функции без странични ефекти - колкото и пъти да ги извиквате с едни и същи параметри, ще връщата същия резултат и няма да променят никакво глобално/локално състояние.
  • От друга страна езикът не е pure, така че може да има странични ефекти, просто е хубаво да се отбягват.
  • Функции от по висок ред - приемащи или връщащи (или и двете) други функции. Като тези от примера по-горе в модула Enum
  • Композиция на функции. Подобно на тези в математиката F = f(g) и F(x) = f(g(x)). Elixir идва с улеснен синтаксис за това x |> g |> f.
  • Рекурсия. Рекурсия се обяснява най-лесно с рекурсия.

Всички тези неща са ви познати, но защо са плюс?

В днешно време компютрите имат процесори с няколко ядра, а когато deploy-ваме нещо на production често искаме програмата ни да работи, използвайки ресурсите на много виртуални или не-толова-виртуални машини. За де се използват добре и максимално тези ресурси, трябва да ги ползваме едновременно. С други думи когато програмата ни върви на едно ядро/процесор/компютър и когато върви на много тя трябва да се държи по идентичен, стабилен начин. Може би се досещате че функционалните езици, които залагат на минимални странични ефекти и immutablility много по лесно постигат това. Все пак програмата е просто конвейер на данни - представя ги, трансформира ги, транспортира ги, ако поддържа много и различни състояния на тези данни всичко става по-сложно.

Защо Elixir? Защото е Erlang!

Да добавим към плюсовете от това че е функционален език и това че Elixir е всъщност по-лесен за писане и разбиране Erlang - получваме език създаден за паралелелизъм и конкурентност. Erlang е езикът с който са написани голяма част от системите позволяващи десетки хиляди паралелни телефонни разговори. Платформата е създадена с тази цел и в днешно време става все по-привлекателна.

Тъй като Elixir e млад език е добре да разгледаме кой и защо ползва основата му - Erlang:

  • Amazon - за базата данни SimpleDB
  • Yahoo! - за URL bookmarking
  • Facebook - real-time съобщенията
  • WhatsApp - написана на Erlang, real-time съобщения, купена от Facebook
  • T-Mobile, Motorola, Ericsson - за SMS услуги и 3G мрежи. Ericsson са създатели на Erlang.
  • RabbitMQ - AMQP имплементация
  • CouchDB - популярна база данни
  • Riak - data store

Като цяло виждаме че Erlang се ползва главно за бази данни и real-time комуникации. Езикът е много надежден, справя се блестящо в конкурентна среда, има много силни инструменти за monitoring и availability.

Разбира се Erlang си има и минусите, да речем няма истински низове и добри инструменти за web разработка. Нещо което е допълнено от Elixir.

Защо Elixir? Заради хората и проектите около него!

Тук е времето да кажем няколко думи за Phoenix.

  • Доста лесен начин за писане на големи лесно-дистрибутирани web приложения.
  • Много от плюсовете идващи от основите на Erlang са валидни и използвани при Phoenix.
  • Лесно е да се достигне до нещо работещо, подобно на Rails - framework-ът, който направи Ruby популярен език.
  • Phoenix има вградена поддръжка на real-time комуникация.
  • Също така е и доста по лесен за индивидуализиране от Rails.

Други важни инструменти са Mix и Hex, които позволяват лесна поддръжка на Elixir библиотеки и справяне с проблеми с различни техни версии. Също така е доста лесно да си публикувате библиотека или да генерирате скелета ѝ с тях.

Интересни връзки:

Защо Elixir? Заради синтаксиса!

Elixir е динамично-типизиран език. Това означава че при дефиниции на променливи или аргументи на функции, не се задават типовете им. Това е минус за някои хора. Има и защо - типовете помагат за намиране на грешки по време на компилация. Добрата новина е че има начин за задаване на типове (подобно на Haskell) и тяхната проверка. Това не е тема на тази статия, но е редно да се спомене. Плюс на динамичните езици е че са по лесни за писане и интерпретиране.

Нека да започнем с дефиниране на анонимни функции, както знаем функциите са основните градивни единици на програмата:

fn (x) -> x * x end

Това е функцията x2. Такава функция може да се извика така:

(fn (x) -> x * x end).(3)
# -> 9

Също така, анонимна функция може да се присвои на променлива:

f = fn (x) -> x * x end
f.(3)
# -> 9

Както казахме, променливите се дефинират без да се задава тип. Те си получават типа взависимост от стойността им. Да речем f в този пример е от тип #Function. Ето кратък пример за други типове:

5               # -> integer
0xcafe          # -> integer, the same as 51966
5.5             # -> float
false           # -> boolean
:dalia          # -> atom
"elixir"        # -> string
[1, 2, 3]       # -> list
[a: 1, b: 2]    # -> keyword list
{1, 2}          # -> tuple
fn (x) -> x end # function
~r/\d+/         # regular expression
%{a: 1, b: 2}   # map

Статията няма за цел да задълбава в типовете, но нека кажем някои интересни неща:

  • Списъците в Elixir не са масиви. Те са свързани списъци.
  • Кортежите се ползват главно за pattern matching, за който ще стане въпрос след малко.
  • Erlang няма низове, има списък от букви, но не и истински стрингове. Elixir има - UTF-8 низове.
  • 'abc' и "abc" са различни типове - единичните кавички са просто опростен синтаксис за списъка [97, 98, 99].
  • Атомите са много подобно нещо на символите в Ruby. Добри са за кодове върнати от функции или ключове в maps или keyword lists.

Друго интересно свойство на езика - операторът = сравнява двете страни на израза (pattern matching), ако те са съпоставими сравнението е успешно, ако не са, се получава грешка (MatchError). Тази природа на оператора позволява по лесно дефиниране на променливи, както и проверки. Няколко примера:

4 = 4                 # Интерпретира се без грешка
5 = 4                 # Грешка - MatchError
a = 4                 # Няма грешка, стойността на променливата 'а' става 4
4 = b                 # Грешка - променливата b не съществува.
4 = a                 # Успех - а съществува и стойносста ѝ е 4
{d, e, 5} = {7, 6, 5} # Успех, d става 7, e става 6
f = fn
  (5) -> {:ok, 5}
  (x) -> {:error, x}
end
{:ok, x} = f.(5)     # Успех, x получава стойност 5
{:ok, x} = f.(6)     # Грешка, резултатът е {:error, 6}, и няма успех.

По този начин кодът може да стане доста лесен за четене и разбиране, както и за проверки. За да не става прекалено дълга статията ще покажем само още две неща.

Първо - модули. Модулите са колекции от именовани функции. Пример:

defmodule MyModule do
  def square(x) do
    x * x
  end
end

Това е модул с функция. Същата тази функция x2. Може да се извика така: MyModule.square(3). В един модул може да има множество функции. Някои от функциите може да са видими само вътре в модула, просто вместо с def се дефинират с defp. В общи линии един или повече такива модули представляват програма в Elixir. Модулите могат да се ползват за дефиниция на нещо като типове - struct-ове. Има начин за дефиниране на абстрактни поведения, които се имплементират от модули. Ако ви става интересно, започнете да учите Elixir, ще стане още по-интересно.

Последното нещо което ще ви покажем, с цел да ви привлечем към Elixir са процесите. Те са най-простият начин да се изпълнят няколко задачи в езика. Тук не става дума за OS-ниво процеси. Всичко написано на Elixir се изпълнява в такива процеси, те са изолирани един от друг, много леки откъм ресурси (CPU/RAM), могат да бъдат хиляди без да натоварват машината на която вървят.

Процесите в Elixir/Erlang се създават със spawn.

pid = spawn fn -> 2 * 21 end # Тази функция ще се изпълни в нов процес конкурентно на текущия.
# pid e от #PID тип. Нещо подобно : #PID<0.42.0>
Process.alive?(pid) # false, тъй като функцията е много проста и се е изпълнила светкавично

# Можем да ползваме pid-а на текущия процес с:
self()
Process.alive?(self()) # true

Виждате колко лесно е да изпълним код в конкурентен процес. Тези процеси могат да комуникират помежду си. Те нямат споделено състояние, но могат да си пращат съобщения.

send self(), {:howdy, "Как си?"}

receive do
  {:howdy, message} -> IO.puts(message)
  {_, message} -> IO.puts("Няма значение")
end
# Ще изпечата 'Как си?

Можем да използваме send за да изпращаме съобщения до процеси. Аргументите му са PID и съобщението което искаме да изпратим. В процесът, който иска да получава съобщения се използва receive с блок - за всеки pattern на съобщение какво да се случи. receive “слуша” до получаване на съобщението. В примера ние изпратихме съобщение от текущия процес към него самия, но по същия начин се изпращат съобщения към произволни процеси.

Това е само началото на темата. Процесите могат да са свързани, да изпълняват периодично задача, да имат състояние, даже има framework който спомага за наблюдение и управление на процеси, как и кога да ги рестартира или съживи. Ако ви е интересна темата, започнете да учите Elixir.

Един много удобен, полезен и интересен линк свързан със синтаксиса -> https://media.pragprog.com/titles/elixir/ElixirCheat.pdf

Защо Elixir?

  • Защото е модерен, бърз и конкурентен език.
  • Защото хората зад и около него са опитни.
  • Защото основата му, Erlang, е стабилна и доказана.
  • Защото е функционален език.
  • Защото е лесен за писане и четене.
  • Защото е подходящ за писане и поддръжка на много типове приложения.
  • Защото ще разшири кръгозора ви.

Ако тази статия ви накара да се заинтересувате от Elixir, имаме новина - ще има следващи статии. Те ще са свързани с програмата на курса “Функционално програмиране с Elixir” във ФМИ. Благодарим ви че отделихте време да прочетете това, дано да си е струвало.