Защо да учим и използваме Elixir?


Това е статия към първата лекция от курса Функционално програмиране с Elixir, провеждан във ФМИ през летния семестър на 2017/2018 година. В нея ще отговорим на въпроса защо бихте учили и използвали Elixir, но нека първо малко предистория.

Erlang

Erlang е функционален език, разработен в средата на 80-те години от Ериксон, с цел писане на телеком програми. Въпреки, че мотивацията за създаването му е водена от спецификата на сферата на телекомуникациите, Erlang не е специлизиран само за тази област. Eдна от по-рядко споменаваните причини[1] за създаването му е желанието да се заменят използваните дотогава специализиран хардуер и софтуер (включително и операционна система). Erlang няма експлицитна поддръжка за програмиране на телефони или каквото и да е било друго телеком оборудване. Няма и изисквания за специален хардуер и операционна система. Вместо това езикът предлага решения за техническите изисквания и проблеми, срещани при създаването на подобен тип софтуер:

  • Конкурентност
  • Скалируемост
  • Дистрибутираност
  • Толерантност към грешки
  • Обновления без спиране на програмата
  • Бързо отговаряне на всички заявки дори при високо натоварване

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

Erlang предлага една различна гледна точка [2] и дори никога да не го използвате, то идеите му и подхода към решаване на определен тип проблеми са напълно приложими при широк набор от технологии.

Къде е мястото на Erlang днес?

Добре де, но колко от нас пишат софтуер за телекоми или софтуер с подобни изисквания? Всъщност това са доста от нас. Изискванията към съвременните уеб приложения се припокриват напълно с изискванията, с които са се съобразили създателите на Erlang. Огромно предимството е да имаш език и платформа, които се изградени от основите си за задоволяването на тези изисквания, без нуждата от външни библиотеки, които да имплементират липсващата функционалност в езика и които заобикалят неговите недостатъци.

Ad-hoc Erlang implementation

Ако искате да разберете повече за гаранциите, които ни дава BEAM[3] - виртуалната машина върху която се изпълнява Erlang - то най-бързият начин е да изгледате ето тази презентация[4].

Elixir

Защо говорим толкова за Erlang в статия, която носи името Защо да учим и използваме Elixir? Best part of Elixir tweet

Elixir е функционален език. Разработката му започва през 2011 година, а версия 1.0 се появява през септември 2014г.

Elixir се изпълнява върху BEAM. Той може да използва всичко, написано на Erlang. И доста често го прави. Това ни дава възможността да съчетаваме нов и модерен език с библиотеки, издържали теста на времето. Извикването на Erlang код от Elixir не носи допълнително забавяне и нужда от използване на криптичен синтаксис или преобразувания на типове. Нека сравним разликите на извикване на Elixir и Erlang функция от даден модул:

Elixir:

DateTime.utc_now()

Erlang:

:crypto.strong_rand_bytes(64)

Elixir модулите започват с главна буква, а Erlang модулите започват с :и малка буква.

Нека да разгледаме една малка част от нещата, заради които ние харесваме Elixir/Erlang:

  • Функционален език - непроменими(immutable) и персистентни(persistant) структури от данни, pattern matching, функции от по-висок ред, композиция на функции.
  • Баланс между чисти(pure) и нечисти функции. Тук някои от заклетите фенове на функционалното програмиране може да не се съгласят. Но някои неща трябва да бъдат пожертвани на олтара на продуктивността.
  • Процеси на ниво език - звучи скучно (може би), но това е основата, на която се изгражда толкова известната конкурентност и дистрибутираност на Erlang/Elixir.
  • OTP(Open Telecom Platfom) - това е платформа, която вече не се отнася за телекомите, но името е останало. OTP вече е много повече неща - Erlang и OTP са нещо цяло и неделимо.

Грешно е да смятаме Elixir като конкурент, който ще убие Erlang. Напротив, Elixir е едно от най-хубавите неща, които можеше да му се случат. Erlang, който е неговата солидна основа, търпи все по-бурно развитие.

Но за да се появи Elixir и да правим курс за него, а не за Erlang, то той трябва да поставя нещо ново на масата.

  • Elixir е Erlang. Всичко, което е плюс на Erlang, е плюс и на Elixir.
  • Но Elixir е и нещо повече.
  • По-добри инструменти в сравнение с Erlang.
  • Хубава документация. Създателят на езика твърди, че грешки в документацията трябва да се третират като грешки в самата програма.
  • Зад езика и неговата екосистема стоят опитни, отзивчиви и интелигентни хора. За да не остане това твърдение безпочвено всеки може да разгледа секцията с Issues в github - на каква част и за колко време от проблемите самият създател на езика или някой от екипа са отговорили. Част от тези хора солидно допринасят и за развитието на Erlang/OTP. Прави впечатление също и броя отворени Issues - към момента на писане на тази статия те са 20 в Elixir и 33 в Erlang. Кратко сравнение (но не напълно правилно и честно) с този брой при Rust и Go - и двата случая броят е > 3000, TypeScript - 2500.
  • Удобен и красив синтаксис. Erlang има минималистичен синтаксис, дори прекалено. Това води до доста boilerplate и повторения в кода. Липсата на макроси подчертава този проблем.
  • Мощни макроси. За разлика от тези в С/С++, макросите в Elixir не работят върху стрингове, ами върху AST (Abstract Syntax Tree).
  • Полиморфизъм чрез протоколи.
  • Pipe оператор |>. С негова помощ кодът е безспорно по-красив, удобен, четим, лесен за писане и за промяна. Едно скрито предимство от това е невероятната консистентност, която той вкарва в стандартната библиотека на езика. За да e по-лесно използването на |>, то е прието функциите да приемат като първи аргумент данните, върху които работят.

Нека разгледаме един пример за разликата във вида на кода при използването на |>.

Целта е да извършим поредица от трансформации върху списък от числа.

Един начин по който бихме написали това е:

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

Ами ако не искаме да използваме толкова много временни променливи? Можем да вложим функциите:

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

В този случай четимостта клони към нула, а добавянето на нова трансформация не е никак лека задача.

Тук идва на помощ|>. Всичко, което той прави е да подаде резултата от лявата си страна като първи аргумент на израза в дясната си страна. Нищо повече, нищо по-малко, но това е достатъчно, за да можем да превърнем нашия код в:

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

Ами лошите страни на Erlang/Elixir?

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

  • Липсата на достатъчно и развити библиотеки. Тъй като Elixir е нов език, а Erlang няма популярността на Java/Python/Ruby, то понякога ще се случва да няма библиотеката, която очаквате. Понякога ще се случва да има единствено Erlang библиотека, която без проблеми може да използвате, но ще трябва да научите и малко Erlang ако искате да четете кода ѝ. Често ще се налага да го правите, защото документацията куца. Има и Elixir библиотеки с лоша документация - АWS библиотеката е един такъв пример.
  • Заблудата, породена от изказвания на хора, незапознати с езика. Често срещано оплакване е, че синтаксисът около конкурентността не е толкова прост и минимален както в Go. Това е пордено от фактът, че конкурентността в Elixir решава проблеми свързани с high availability, докато в Go се използва чисто и просто за паралелизъм. В Elixir паралелизмът не е бил движеща сила при взимането на дизайн решенията,а е следствие (при това доста успешно) от начина на имплементация
  • Езикът не е толкова очевиден (на пръв поглед) за интроспекция. Ако искате да намерите дължината на списък, то функцията в модула List ли се намира? Всъщност откривате, че няма нито List.size, нито List.length. Дължината се намира с Enum.count, която вътрешно използва :erlang.length (достъпна и като Kernel.length или просто length - Elixir вътрешно използва директно голяма част от Erlang). Но с Enum.count можем да намерим и броя елементи в map (и всичко, което имплементира Enumerable), което става благодарение на протоколите - нещо, което няма в Erlang.

Проблемът с библиотеките е проблем на всеки език в този етап от развитието си. За да стане ясно от самото начало, тук не говорим за важни библиотеки - HTTP сървър/клиент, библиотеки за работа с бази данни, уеб фреймуърк, JSON декодери и т.н. са налични и работят. Става въпрос за малките неща. За всеобща радост това не е проблем със самия език и е лесно, но бавно поправим. На курса по Elixir във ФМИ ще даваме бонус точки за принос към някоя библиотека с отворен код. А защо не и да създадете нова, която запълва съществуваща дупка?

Ресурси:

[0] Уводна лекция към курса Функционално програмиране с Elixir

[1] The true story why Erlang was invented

[2] The zen of Erlang

[3] Hitchhiker’s Tour of the BEAM

[4] Solid ground by Saša Jurić

Erlang официален сайт

Elixir официален сайт