Защо 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 библиотеки и справяне с проблеми с различни техни версии. Също така е доста лесно да си публикувате библиотека или да генерирате скелета ѝ с тях.
Интересни връзки:
- Добро място за задаване на въпроси или просто четене на мнения е https://elixirforum.com
- Списък с компании които се занимават с Elixir - https://github.com/doomspork/elixir-companies
- Библиотеки на Elixir по категории - https://github.com/h4cc/awesome-elixir
- Главна страница на Elixir - http://elixir-lang.org
Защо 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” във ФМИ. Благодарим ви че отделихте време да прочетете това, дано да си е струвало.