У Elixir более приятный синтаксис, чем у Erlang?
Май 2018
Когда я впервые решил попробовать Elixir, меня поразила его процесс-ориентированность. Я влюбился в нее по уши и понял, что Elixir на несколько шагов ближе к объектно-ориентированному программированию, чем любой существующий мейнстрим ОО-язык.
Чтобы научиться адекватно использовать процессы, нужно постигнуть OTP -- фреймворк для построения приложений на Erlang/Elixir. Попытавшись осилить генсервера и деревья супервизоров с помощью книги Exlixir in Action, я потерпел крах. Возможность написать одно и тоже несколькими способами сильно тормозило меня. Например, Map в Elixir можно писать двумя разными способами:
# Если ключ словаря -- атом, то можно использовать двоеточие iex(1)> map_1 = %{key: "value"} %{key: "value"} # Если поставить двоеточие перед строкой, она превратиться в атом iex(2)> map_2 = %{"key": "value"} %{key: "value"} # Если ключом должна быть строка, то нужно использовать "=>" iex(3)> map_2 = %{"key" => "value"} %{"key" => "value"} # Если ключ -- атом, то к нему можно получить доступ через точку iex(4)> map_1.key "value" # или через явное указание атома iex(5)> map_1[:key] "value" # А если ключ -- строка, то доступ по точке уже не работает iex(6)> map_2.key ** (KeyError) key :key not found in: %{"key" => "value"} # и можно использовать только явное указание iex(6)> map_2["key"] "value"
Это, конечно, мелочи, но они увеличивают кривую обучения. Когда дело доходит до конфигурирования или паттерн-матчинга с помощью Map, то хочется застрелиться.
В Erlang работа с Map более очевидна:
%% Ключ словаря -- атом 1> Map1 = #{key => "value"}. #{key => "value"} %% ключ словаря -- строка. Разницы нет. 2> Map2 = #{"key" => "value"}. #{"key" => "value"} %% Получение значения по ключу одинакова и с атомом, 3> maps:get(key, Map1). "value" %% и со строкой. 4> maps:get("key", Map2). "value"
При написании генсервера или супервизора в Elixir приходится добавлять дополнительный уровень вложенности:
defmodule KV.GenServer do use GenServer def start_link(opts) do GenServer.start_link(__MODULE__, :ok, opts) end def lookup(server, name) do GenServer.call(server, {:lookup, name}) end def create(server, name, value) do GenServer.cast(server, {:create, name, value}) end def init(:ok) do {:ok, %{}} end def handle_call({:lookup, name}, _from, names) do {:reply, Map.fetch(names, name), names} end def handle_cast({:create, name, value}, names) do if Map.has_key?(names, name) do {:noreply, names} else {:noreply, Map.put(names, name, value)} end end end
Erlang же говорит нам: «Ребята, не создавайте сущности. Файл -- это уже модуль, просто дайте ему имя».
-module(kv_genserver). -behaviour(gen_server). -export([start_link/1, lookup/2, create/3]). -export([init/1, handle_call/3, handle_cast/2]). start_link(Opts) -> gen_server:start_link(?MODULE, ok, Opts). lookup(Server, Name) -> gen_server:call(Server, {lookup, Name}). create(Server, Name, Value) -> gen_server:cast(Server, {create, Name, Value}). init(ok) -> {ok, #{}}. handle_call({lookup, Name}, _From, Names) -> {reply, maps:get(Name, Names, error), Names}. handle_cast({create, Name, Value}, Names) -> case maps:is_key(Name, Names) of true -> {noreply, Names}; false -> {noreply, maps:put(Name, Value, Names)} end.
При большом объеме кода, отсутствие лишнего уровня вложенности улучшает читаемость. И это важно, мы же читам код больше времени, чем пишем. Также в Erlang не приходится все конструкции оборачивать в do ... end, а однострочная и лямбда функция не отличается от обычной:
-module(example). -export([one_line_func/0, multi_line_func/0, lambda_inside_me/0]). one_line_func() -> io:format("One line func"). multi_line_func() -> io:format("First line"), io:format("Second line"). lambda_inside_me() -> Lambda = fun() -> io:format("Lambda") end, Lambda().
В Elixir однострочные, многострочные и лямбда функции разные:
defmodule Example do def one_line_func, do: IO.puts("Only one line") def multi_line_func do IO.puts("First line") IO.puts("Second line") end def lambda_inside_me do lambda = fn() -> IO.puts("Lambda") end lambda.() end end
Elixir стоит использовать из-за сообщества: библиотек появляется много, новая функциональность добавляется быстро, собираются митапы и конференции, а интернет медленно, но верно начинает пестрить вакансиями. Но я всем советую сначала изучить Erlang по книге "Learn You Some Erlang for Great Good!", чтобы лучше понять процессы и OTP и почувствовать вкус лаконичного синтаксиса, который, на мой взгляд, намного приятнее, чем у Elixir.