Contents
In the first section of the manual you can find an overview of all related to Ruby features of ErlPort and a lot of examples mostly with using of Erlang shell. Make sure you read Downloads page and installed ErlPort or know how to start Erlang shell with ErlPort from the sources directory.
If you read main Documentation page you should know that before any use of ErlPort you need to start a Ruby instance. A Ruby instance is basically an operating system process which represented in Erlang by Erlang process.
To start an instance of Ruby one of the variants of ruby:start or ruby:start_link functions should be used. The ruby:start/0 function will start Ruby instance with the default parameters.
To stop the running Ruby instance ruby:stop/1 function should be used with the instance id as the parameter.
And of course you can run more than one Ruby instance, but be careful to not create way too many because they can waste all the OS resources.
The following is an example of start/stop API:
1> {ok, RubyInstance1} = ruby:start(). {ok,<0.34.0>} 2> {ok, RubyInstance2} = ruby:start(). {ok,<0.36.0>} 3> ruby:stop(RubyInstance1) ok 4> ruby:stop(RubyInstance2) ok
To call a function in Ruby ruby:call/4 function should be used which accepts the instance id and File, Function and Arguments for Ruby function:
1> {ok, R} = ruby:start(). {ok,<0.34.0>} 2> ruby:call(R, '', 'Integer', [<<"2">>]). 2
Of course in Ruby you can also have hierarchies of files and modules so ruby:call/4 supports /-separated names for File and ::-separated for Function arguments:
3> ruby:call(R, 'rss/atom', 'RSS::Atom::Feed::new::version', []). <<"1.0">>
In case of any error during the function call an exception of class error will be generated in the following form:
error:{ruby, ExceptionClass, ExceptionArgument, ReversedStackTrace}
For example:
4> try ruby:call(R, unknown, unknown, []) 4> catch error:{ruby, Class, Argument, StackTrace} -> error 4> end. error 5> Class. 'LoadError' 6> Argument. <<"no such file to load -- unknown">> 7> StackTrace. [<<"-e:1">>,<<"-e:1:in `require'">>, <<"/../erlport/priv/ruby1.8/erlport/cli.rb:94">>, <<"/../erlport/priv/ruby1.8/erlport/cli.rb:41:in `main'">>, <<"/../erlport/priv/ruby1.8/erlport/erlang.rb:135:in `start'">>, <<"/../erlport/priv/ruby1.8/erlport/erlang.rb:191:in `_receive'">>, <<"/../erlport/priv/ruby1.8/erlport/erlang.rb:231:in `call_with_e"...>>, <<"/../erlport/priv/ruby1.8/erlport/erlang.rb:192:in `_receiv"...>>, <<"/../erlport/priv/ruby1.8/erlport/erlang.rb:215:in `inc"...>>, <<"/../erlport/priv/ruby1.8/erlport/erlang.rb:215:in "...>>]
And of course don't forget to stop the instance at the end:
8> ruby:stop(R). ok
If you want to call a function from your own Ruby file in most cases you need to set the Ruby lib. You can do it with ruby:start/1 function or RUBYLIB environment variable. The ruby:start/1 also can be used to change the default Ruby interpreter. For example let's create a simple Ruby file /path/to/my/modules/version.rb:
def version "#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}" end
Now we can set path to this module in ruby:start/1 like this:
1> {ok, R} = ruby:start([{ruby_lib, "/path/to/my/modules"}, 1> {ruby, "ruby1.9.3"}]). {ok,<0.34.0>} 2> ruby:call(R, version, version, []). <<"1.9.3-p0">> 3> ruby:stop(R). ok
ErlPort uses Ruby erlport/erlang.rb file with ErlPort::Erlang module as an interface to Erlang. Namely ErlPort::Erlang::call() function allows to call Erlang functions from Ruby. The function accepts Module and Function arguments as Symbol() (and ErlPort::ErlTerm::EmptySymbol() for Ruby 1.8.*) object and Arguments as an Array(). Currently each Erlang function will be called in a new Erlang process. Let's create the following Ruby module in pids.rb file in the current directory which will be added to Ruby lib path automatically by Ruby:
include ErlPort::Erlang def pids pid1 = call(:erlang, :self, []) pid2 = call(:erlang, :self, []) [pid1, pid2] end
Now we can call this function from Erlang:
1> {ok, R} = ruby:start(). {ok,<0.34.0>} 2> ruby:call(R, pids, pids, []). [<0.36.0>,<0.37.0>] 3> ruby:stop(R). ok
To simplify the demonstration the next example will use the call chaining so Ruby to Erlang calls will be initiated from Erlang shell. The following example also demonstrate the communication between two Ruby instances:
1> {ok, R1} = ruby:start(). {ok,<0.34.0>} 2> {ok, R2} = ruby:start(). {ok,<0.36.0>} 3> ruby:call(R1, '', 'Process::pid', []). 5196 4> ruby:call(R2, '', 'Process::pid', []). 5198 5> ruby:call(R1, 'erlport/erlang', call, 5> [ruby, call, [R2, '', 'Process::pid', []]]). 5198 6> ruby:stop(R1). ok 7> ruby:stop(R2). ok
So the command #5 actually calls ErlPort::Erlang::call() function for instance R1, which calls Erlang function ruby:call/4, which in order calls Ruby function Process::pid() for instance R2.
To send a message from Erlang to Ruby first a message handler function on Ruby side should be set. The message handler function can be set with ErlPort::Erlang::set_message_handler() function. The default message handler just ignore all the incoming messages. And if you don't need to handle incoming message anymore the default handler can be set again with ErlPort::Erlang::set_default_message_handler() function.
Be careful when you write a message handling function because the function can also get some unexpected messages which probably should be ignored and in case of any error in the message handler the whole instance will be shut down.
To demonstrate message sending from Erlang to Ruby we will first create the following module in the current directory in a file handler.rb:
include ErlPort::Erlang def register_handler dest set_message_handler {|message| cast dest, message } :ok end
This message handler just send all messages to the selected Erlang process.
To send a message to Ruby ruby:cast/2 function can be used and also all unknown to ErlPort messages will be redirected to the message handler.
1> {ok, R} = ruby:start(). {ok,<0.34.0>} 2> ruby:call(R, handler, register_handler, [self()]). ok 3> ruby:cast(R, test_message). ok 4> flush(). Shell got test_message ok 5> R ! test_message2. test_message2 6> flush(). Shell got test_message2 ok 7> ruby:stop(R). ok
It's very easy to send a message from Ruby to Erlang - you just need to know the pid() or registered name of the destination process. The function ErlPort::Erlang::cast() accepts two arguments - the id of the destination process and a message which can be any supported data type according to Data types mapping. And of course you can send messages to any other ErlPort process.
The following is a demonstration of message sending from Ruby:
1> {ok, R} = ruby:start(). {ok,<0.34.0>} 2> ruby:call(R, 'erlport/erlang', cast, [self(), test_message]). undefined 3> flush(). Shell got test_message ok 4> register(test_process, self()). true 5> ruby:call(R, 'erlport/erlang', cast, [test_process, test_message2]). undefined 6> flush(). Shell got test_message2 ok 7> ruby:stop(R). ok
ErlPort only supports a minimal set of data types to make sure the types are orthogonal - can be created and meaningful in any language supported by ErlPort. In addition ErlPort also supports language specific opaque data type containers so for example Ruby instances can exchange any serializable data type. But sometimes it's better to use rich inter-language data types in which case custom data types can be used.
There are two functions to support custom data types:
Both of the functions can be reset to the default, which just pass the value unmodified, with ErlPort::Erlang::set_default_encoder() and ErlPort::Erlang::set_default_decoder() functions correspondingly. Note also that there's no support for automatic traversing of container data types so it should be implemented by encoder/decoder functions if needed.
To give you a feeling how it works the following file in the current directory with name date_type.rb will add the partial support to ErlPort for Time() objects:
include ErlPort::ErlTerm include ErlPort::Erlang def setup_date_type set_encoder {|v| date_encoder v} set_decoder {|v| date_decoder v} :ok end def date_encoder value if value.is_a? Time value = Tuple.new([:date, Tuple.new([value.year, value.month, value.day])]) end value end def date_decoder value if value.is_a? Tuple and value.length == 2 and value[0] == :date year, month, day = value[1] value = Time.utc(year, month, day) end value end def add date, sec date + sec end
The date_type module can be used in Erlang shell like this:
1> {ok, R} = ruby:start(). {ok,<0.34.0>} 2> ruby:call(R, date_type, setup_date_type, []). ok 3> Date = ruby:call(R, '', 'Time::utc', [2012, 12, 31]). {date,{2012,12,31}} 4> ruby:call(R, date_type, add, [Date, 60 * 60 * 24]). {date,{2013,1,1}} 5> ruby:stop(R). ok
As a convenient feature ErlPort also supports redirection of Ruby`s STDOUT to Erlang which can be used for example for debugging. For example:
1> {ok, R} = ruby:start(). {ok,<0.34.0>} 2> ruby:call(R, '', puts, [<<"Hello, World!">>]). Hello, World! undefined 3> ruby:stop(R). ok
Here you can find complete description of data types mapping, Erlang functions, Ruby functions and environment variables supported by ErlPort.
The following table defines mapping of Erlang data types to Ruby data types:
Erlang data type | Ruby data type |
---|---|
integer() | Integer() |
float() | Float() |
atom() | Symbol() and ErlPort::ErlTerm::EmptySymbol() in Ruby 1.8.* |
true | true |
false | false |
undefined | nil |
binary() | String() |
tuple() | ErlPort::ErlTerm::Tuple() |
list() | Array() |
improper_list() | ErlPort::ErlTerm::ImproperList() |
Opaque Ruby data type container | Ruby data type |
Opaque data type container | Opaque data type container |
And here is the table of Ruby to Erlang data types mapping. The types mapping between Erlang and Ruby are practically orthogonal:
Ruby data type | Erlang data type |
---|---|
Integer() | integer() |
Float() | float() |
Symbol() and ErlPort::ErlTerm::EmptySymbol() in Ruby 1.8.* | atom() |
true | true |
talse | false |
nil | undefined |
String() | binary() |
ErlPort::ErlTerm::Tuple() | tuple() |
Array() | list() |
ErlPort::ErlTerm::ImproperList() | improper_list() |
Other Ruby data type | Opaque Ruby data type container |
Opaque data type container | Opaque data type container |
The following classes can be found in erlport/erlterms.rb file.
Start Ruby instance with options. The Options argument should be a list with the following options.
General options:
Ruby related options:
The Ruby programs search path. The Path variable can be a string in RUBYLIB format or a list of paths. The priorities of different ways to set the modules search path is as follows:
Call Ruby function. The Instance variable can be a pid() which returned by one of the ruby:start functions or an instance name (atom()) if the instance was registered with a name. The File and Function variables should be atoms and Arguments is a list.
In case of any error on Ruby side during the function call an exception of class error will be generated in the following form:
error:{ruby, ExceptionClass, ExceptionArgument, ReversedStackTrace}
The same as ruby:call/4 except the following options can be added:
All the following functions can be found in erlport/erlang.rb file.
The following environment variables can change the default behavior of ErlPort:
The default search patch for Ruby programs. The same as RUBYLIB environment variable supported by Ruby. The priorities of different ways to set the programs search path is as follows: