Наконец-то свершилось, я больше не разрабатываю приложения на Rails. Теперь Elixir — мой основной инструмент, и я рад этому.
Тем не менее всё ещё существует множество наработок в Ruby-мире, аналогов которых пока нет в Elixir. Чтобы обойти эту проблему, я решил выяснить могут ли оба мира взаимодействовать друг с другом. И ответ — да! Они могут.
Я попробую описать несколько способов, как это сделать.
Чтобы не смешивать всё в одну кучу, материал будет разбит на несколько статей, каждая из которых будет описывать только один способ. Первая — про ExPort / ErlPort.
Примечание: ExPort также позволяет взаимодействовать с Python.
Этот способ запускает интерпретатор Ruby и предоставляет вспомогательный API для преобразования типов.
Итак, давайте создадим простое приложение на Elixir, использующее Ruby-код.
$ mix new elixir_ruby_app
$ cd elixir_ruby_app
Добавьте пакет export
в списки deps
и applications
в mix.exs
def application do
[applications: [:export]]
end
def deps do
[
{:export, “~> 0.0.7”},
{:erlport, github: “hdima/erlport”, manager: :make}
]
end
Скачайте зависимости
$ mix deps.get
Готово. Теперь давайте создадим директорию для Ruby-скриптов. Я выбрал priv, посколько она будет включена в релизную сборку, генерируемую exrm или distilery.
$ mkdir -p priv/ruby
Создайте скрипт fancy.rb в этой директории со следующим содержимым:
def sum_two_integers(one, another)
result = one + another
Tuple.new(
[:ok, result]
)
end
Код, вызывающий функцию из Ruby, выглядит так:
defmodule ElixirRubyApp do
@ruby_dir Application.app_dir(:elixir_ruby_app, "priv/ruby")
use Export.Ruby
def sum_two_integers_in_ruby(one, another) do
{:ok, ruby} = Ruby.start(ruby_lib: @ruby_dir)
ruby |> Ruby.call(sum_two_integers(one, another), from_file: "fancy")
end
end
Настало время волшебства. Запустите iex и вызовите функцию:
$ iex -S mix
erlang/OTP 19 [erts-8.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.3.3) — press Ctrl+C to exit (type h() ENTER for help)
iex(1)> ElixirRubyApp.sum_two_integers_in_ruby(1, 2)
{:ok, 3}
Бац! Мы получили результат из Ruby. Вы можете использовать любые Ruby gems, которые захотите. Например, я использую гем icalendar для разбора ical-файлов.
Но мы ещё не закончили! Как насчёт вызова Elixir-функций из Ruby?
Давайте попробуем! Создайте файл fancy.ex:
defmodule ElixirRubyApp.Fancy do
def sum_two_integers_in_elixir(one, another) do
result = one + another
{:ok, result}
end
end
Хорошо, мы передумали и теперь хотим суммировать числа в Elixir, но внутри Ruby. Безумная идея, но почему бы и нет? Изменим наш файл fancy.rb, чтобы он вызывал функцию из Elixir:
include ErlPort::Erlang
def sum_two_integers(one, another)
call('Elixir.ElixirRubyApp.Fancy'.to_sym, :sum_two_integers_in_elixir, [one, another])
end
Запустим снова iex и посмотрим, что произойдёт.
$ iex -S mix
Erlang/OTP 19 [erts-8.1] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.3.3) — press Ctrl+C to exit (type h() ENTER for help)
iex(1)> ElixirRubyApp.sum_two_integers_in_ruby(14,2)
{:ok, 16}
Наша безумная идея сработала без проблем. Прекрасно.
Существуют и другие способы вызывать Ruby из Elixir и наоборот. Но о них мы поговорим в следующих статьях.
Envek
Плохо понял, как работает последний пример, как вообще ExPort и ErlPort работают внутри? Он вызывает интерпретатор Ruby каждый раз при вызове или держит его запущенным?
Source
В примере из статьи интерпретатор Ruby запускается каждый раз, но это только потому что автор вызывает
из тела функции. Если сохранить
ruby
pid в Agent или в GenServer, то интерпретатор Ruby будет держаться запущенным и можно будет повторно к нему обращаться.Работает это всё через механизм межпроцессорного взаимодействия — Erlang Ports.
Последний пример вызывает Ruby-код из Elixir, но при этом вызываемый Ruby-код в свою очередь вызывает функцию из Elixir-кода. По сути это просто демонстрация, что взаимодействие работает в обе стороны.
slayerhabr
Т.е. общение через stdin/stdout? я правильно понял?
Source
По сути да. Только спрятанное за удобными обёртками.