It is common for modern computer systems of any size to be built using more than one programming language. Device drivers are typically written in C, and many integrated development environments (IDEs)—such as Eclipse—and other GUI-heavy systems are written in Java or C#. Lightweight web apps can be developed in Ruby and PHP, and Erlang can provide lightweight, fault-tolerant concurrency. If you need to efficiently manipulate or parse strings, Perl or Python is the norm. The library that solves a particular problem for you may not be written in your favorite language, and you must choose whether to use the foreign library or bite the bullet and recode the whole thing in Erlang yourself.[36]
Interlanguage communication is never simple in natural languages or in programming. In natural languages, we must understand the different ways in which the languages work. Do they contain articles? Do they denote gender? Where do the verbs occur in a sentence? We also must understand how words translate. Does the verb ser in Portuguese mean the same as “to be” in English, for instance? (It doesn’t.) It’s the same for programming languages. Which paradigm do they come from? Are the languages functional, object-oriented, concurrent, or structured? Is an integer in Java the same thing as an integer in Erlang? (It isn’t!)
Interoperation is not only about interlanguage communication, and Erlang/OTP also supports communication by means of XML, ODBC, CORBA, ASN, and SNMP. These assist in Erlang’s growing role as the “distributed glue” joining together single-threaded legacy programs.
Erlang provides a number of mechanisms for interlanguage working: a higher-level model built on distributed Erlang nodes, a lower-level model allowing communication with an external program through a port, and a mechanism for linking programs into the virtual machine (VM) itself, known as linked-in drivers.
The Erlang distributed programming model provides a simple and flexible solution to the high-level question of how other languages can work with Erlang: run the language in other nodes on the same or different machines, making it appear like a distributed Erlang node to which you pass messages back and forth. These nodes provide an environment where foreign programs can be run, yet also communicate with Erlang nodes. To make this communication work, it is possible to use ports or alternatively provide a higher-level model of the Erlang communication primitives in this other language. In either case, there’s a question of how to deal with the elementary types, and different degrees of support for translating between complex data in the two languages can be provided.
In this chapter, we’ll discuss how to build nodes in Java and in C
that can interoperate with Erlang, and we’ll introduce erl_call
, which allows the Unix shell to
communicate with a distributed Erlang node, built on the erl_interface
library.
This, together with the JInterface
Java package,
comes with the standard Erlang distribution. These libraries offer
stability of code and architecture, at some sacrifice in absolute speed.
After this, we will describe how to communicate via ports, and we’ll give
an example of how to interact with Ruby, using the erlectricity
library.
To gain the greatest efficiency in interoperation, you can define a linked-in driver. The problem is that an erroneous linked-in driver will cause the entire Erlang runtime system to leak memory, hang, or crash, so you should use linked-in drivers with extreme care.
The JInterface
Java
package provides a high-level model of Erlang-style processes and
communication in Java. You can use this package alone to give Erlang-style
concurrency in Java, or you can use it as part of a mixed Java/Erlang
distributed system, allowing a Java system to contain Erlang components or
vice versa.
A Java package such as JInterface
consists of a collection of Java classes, mostly beginning with the prefix
Otp
. This section will describe the
most common of them, providing examples of passing messages and handling
data types when communicating between Erlang and Java. You can find
additional information on JInterface
and its Erlang classes in the “Interface and Communication Applications”
section of the Erlang/OTP documentation. The running example for this
section is a rework of the remote procedure call (RPC) example in Chapter 11.
We described Erlang nodes in Chapter 11, where we also introduced distributed programming in Erlang. An Erlang node is identified by its name, which consists of an identifier with a hostname (in short or long form); each host can run a number of nodes, if their names are different.
The OtpNode
class gives the JInterface
representation of an Erlang node:
OtpNode bar = new OtpNode("bar");
This creates the Java object bar
—which we’ll call a
node—that represents the Erlang node bar
, running on the host where the statement
is executed.
You can create a process on this node by creating a mailbox, which is represented by a pid or can be registered to a name. To create the process, use:
OtpMbox mbox = bar.createMbox();
which gives the process a name on creation. Then, pass in the name as a string on construction:
OtpMbox mbox = bar.createMbox("facserver");
You can also do this separately from the creation of the mailbox using the following statement:
mbox.registerName("facserver");
This creates a named process called facserver
. The process will act as a
“factorial server,” sending the factorial of the integers that it
receives to the processes that have sent the integers.
Once you’ve named the mailbox, you can access it using its name.
If its pid is also required—perhaps by a remote Erlang node—use the
self
method on the mailbox:
OtpErlangPid pid = mbox.self();
The JInterface
package
contains a variety of classes that represent, in Java,
various Erlang types. Their methods allow the conversion of native Java
types to and from these representation types, supporting the conversion
of values between the two languages, which is essential for them to work
together effectively.
You saw an example of this in the preceding Java statement: the
class OtpErlangPid
gives the
Java representation of an Erlang pid. The mapping between Erlang types
of atoms, binaries, lists, pids, ports, refs, tuples, and terms is to
the corresponding Java class OtpErlangAtom
,
...
, OtpErlangTuple
, OtpErlangObject
.
Floating-point types in Erlang are converted to either OtpErlangFloat
or
OtpErlangDouble
; integral types are
converted to OtpErlangByte
, OtpErlangChar
, OtpErlangShort
, OtpErlangInt
, OtpErlangUInt
, or OtpErlangLong
, depending on the particular
integral value and sign.
To represent the two special atoms true
and false
, there is the OtpErlangBoolean
class, and Erlang strings—which are lists of integers in Erlang—are
described by OtpErlangString
.
The details of these classes are in the JInterface
documentation; we will use the
classes in the next section as well as in the RPC example.
Erlang processes send and receive messages, and in JInterface
, these operations are provided by
the send
and receive
methods on a mailbox. The interchanged
messages are Erlang terms, and are therefore represented by OtpErlangObject
s in
Java. The following Erlang send
message:
Pid ! {ok, M}
in the mbox
process is given
by:
mbox.send(pid,tuple);
where the pid
variable in Java
corresponds to the Pid
variable in
Erlang, and tuple
[37] represents the Erlang term {ok,
M}
.
A message is received by:
OtpErlangObject o = mbox.receive();
This statement differs from an Erlang receive
in that it performs no pattern
matching on the message. Deconstruction and analysis of the message
follow separately; we’ll show this in the next example.
The following Erlang code sets up a factorial server on the node called bar
on the host STC
:
setup() -> spawn('bar@STC',myrpc,server,[]). server() -> register(facserver,self()), facLoop(). facLoop() -> receive {Pid, N} -> Pid ! {ok, fac(N)} end, facLoop().
The server receives messages of the form {Pid, N}
and sends the result {ok, fac(N)}
back to the Pid
. The next code sample accomplishes the
same thing in Java:
1 import com.ericsson.otp.erlang.*; // For the JInterface package 2 import java.math.BigInteger; // For factorial calculations 3 4 public class ServerNode { 5 6 public static void main (String[] _args) throws Exception{ 7 8OtpNode bar = new OtpNode("bar");
9OtpMbox mbox = bar.createMbox("facserver");
10 11 OtpErlangObject o; 12 OtpErlangTuple msg; 13 OtpErlangPid from; 14 BigInteger n; 15 OtpErlangAtom ok = new OtpErlangAtom("ok"); 16 17 while(true) try { 18o = mbox.receive();
19 msg = (OtpErlangTuple)o; 20 from = (OtpErlangPid)(msg.elementAt(0)); 21 n = ((OtpErlangLong)(msg.elementAt(1))).bigIntegerValue(); 22 OtpErlangObject[] reply = new OtpErlangObject[2]; 23 reply[0] = ok; 24 reply[1] = new OtpErlangLong(Factorial.factorial(n)); 25 OtpErlangTuple tuple = new OtpErlangTuple(reply); 26mbox.send(from,tuple);
27 28 }catch(OtpErlangExit e) { break; } 29 } 30 }
In the preceding example, the concurrent aspects are shown in bold in lines 8, 9, 18, and 26; the remaining code is used to analyze, deconstruct, and reconstruct data values, as well as providing the control loop.
The main program starts by running a node bar
, and a process facserver
on that node. In the main loop,
lines 18–26, a message is received and replied to. The message received
is an Erlang term, that is, an OtpErlangObject
. This
is cast to an OtpErlangTuple
in line
19, and from this the pid of the sender (line 20) and the integer being
sent (line 21) can be extracted. In line 21, the Erlang value is
extracted as a long integer, but is converted to a Java BigInteger
to allow an accurate calculation of
the factorial.
The remainder of the code (lines 22–26) constructs the reply tuple
and sends it. Line 22 constructs an array of objects, containing the
(representation of the) atom ok
(line
23) and the return value factorial(n)
(line 24). This is then converted into a tuple (line 25), before finally being sent back
to the client in line 26.
To interact with the running Java node, you can use the following code, calling
myrpc:f/1
at the prompt:
-module(myrpc). ... f(N) -> {facserver, 'bar@STC'} ! {self(), N}, receive {ok, Res} -> io:format("Factorial of ~p is ~p.~n", [N,Res]) end.
This client code is exactly the same as the code that is used to interact with an Erlang node, and a “Turing test”[38] that sends messages to and from a node should be unable to tell the difference between a Java node and an Erlang node.
In this section, we will explain how to get programs
using JInterface
to run
correctly on your computer.
First, to establish and administer connections between the Java
and Erlang nodes it is necessary that epmd
(the Erlang Port
Mapper Daemon) is running when a node is created. You will recall
epmd
from Chapter 11. You can run it simply by
typing epmd
(epmd.exe
on Windows), but you can test
whether it is already running by typing the following:
epmd
-names
This will list all the names of the running Erlang nodes on the host. This command is useful for checking whether a node you think should be running actually is running.
The system will create a node with the default cookie if none is supplied when the node is started. This may be OK, but if you need to create a node with a given cookie, use the following:
OtpNode bar = new OtpNode("bar", "cookie-value");
If a particular port needs to be used, this is the third argument of a three-argument constructor.
Referring back to the program in the section Putting It Together: RPC Revisited, line 1 of the
program ensures that the JInterface
Java code is imported, but since it is included in the OTP distribution
and not in the standard Java, it is necessary to point the Java compiler
and runtime to where it is held, which is in the following:
<otp-root>/jinterface-XXX/priv/OtpErlang.jar
In the preceding code, <otp-root>
is the root directory of the
distribution, given by typing code:root_dir()
within a running node, and
XXX
is the version number. On Mac OS
X the full path is:
/usr/local/lib/erlang/lib/jinterface-1.4.2/priv/OtpErlang.jar
This value is supplied thus to the compiler:
javac -classpath ".:/usr/local/lib/erlang/lib/ jinterface-1.4.2/priv/OtpErlang.jar" ServerNode.java
and to the Java system:
java -classpath ".:/usr/local/lib/erlang/lib/ jinterface-1.4.2/priv/OtpErlang.jar" ServerNode
The JInterface
library
has more extensive capabilities than you have seen so
far:
It is possible to link to and unlink from Java processes using
the link
and unlink
methods on OtpMbox
.
The example relies on connections between nodes being made
automatically. You can use the ping
method on a node to test whether a
remote node exists; if it does, a connection is made
automatically.
Arbitrary data can be sent between nodes using binary data,
manipulated by the OtpErlangBinary
class.
The OtpConnection
class provides a higher-level mechanism for RPC, just as the
rpc
module does for
Erlang.
Methods on the OtpConnection
class also support control
of tracing.
These and other features are described in more detail in the online documentation.
The erl_interface
library
provides C-side functionality for constructing,
manipulating, and accessing C encodings of Erlang binary terms. Also
included are functions for dealing with memory allocation in term
(de)construction and manipulation, accessing global names, and reporting
errors. In a little more detail:
erl_marshal
,
erl_eterm
, erl_format
, and erl_malloc
For handling the Erlang term format, including memory management. In particular, these provide conversion to and from C structs similar to Erlang terms, allowing higher-level manipulation of data.
erl_connect
and
ei_connect
For providing a connection with Erlang through a distributed Erlang node.
erl_error
erl_global
registry
For providing the facility to store and back up key-value pairs. This provides some of the functionality of ETS tables and can be backed up or restored from a Mnesia table on a linked Erlang node.
In addition, the Erlang external term format is
a representation of an Erlang term as a sequence of bytes: a binary. You
can convert between the two representations in Erlang using the BIFs term_to_binary/1
and
binary_to_term/1
. We discuss this in
more detail in Port Programs.
In this section, we’ll revisit the example of the factorial server just given for Java, this time in C, based on the example in the online Interoperability Tutorial for Erlang:
1 /* fac.c */ 2 3 #include <stdio.h> 4 #include <sys/types.h> 5 #include <sys/socket.h> 6 #include <netinet/in.h> 7 8 #include "erl_interface.h" 9 #include "ei.h" 10 11 #define BUFSIZE 100 12 13 int main(int argc, char **argv) { 14 int fd; /* file descriptor of Erlang node */ 15 16 int loop = 1; /* Loop flag */ 17 int got; /* Result of receive */ 18 unsigned char buf[BUFSIZE]; /* Buffer for incoming message */ 19 ErlMessage emsg; /* Incoming message */ 20 21 ETERM *fromp, *argp, *resp; /* Representations of Erlang terms */ 22 int res; /* Result of the fac call */ 23 24 /* initialize erl_interface (once only) */ 25 erl_init(NULL, 0); 26 27 /* initialize the connection mechanism */ 28 if (erl_connect_init(1, "mycookie", 0) == −1) 29 erl_err_quit("erl_connect_init"); 30 31 /* connect to a running Erlang node */ 32 if ((fd = erl_connect("blah@STC")) < 0) 33 erl_err_quit("erl_connect"); 34 35 while (loop) { 36 /* message received */ 37 got = erl_receive_msg(fd, buf, BUFSIZE, &emsg); 38 39 if (got == ERL_TICK) { 40 /* ignore */ 41 } else if (got == ERL_ERROR) { 42 loop = 0; 43 } else { 44 if (emsg.type == ERL_REG_SEND) { 45 /* unpack message fields */ 46 fromp = erl_element(1, emsg.msg); 47 argp = erl_element(2, emsg.msg); 48 49 /* call fac and send result back */ 50 resp = erl_format("{ok, ~i}", fac(ERL_INT_VALUE(argp))); 51 erl_send(fd, fromp, resp); 52 53 /* free the term storage used */ 54 erl_free_term(emsg.from); erl_free_term(emsg.msg); 55 erl_free_term(fromp); erl_free_term(argp); 56 erl_free_term(resp); 57 } } } } 58 59 int fac(int y) { 60 if (y <= 0) 61 {return 1;} 62 else 63 {return (y*fac(y-1));}; 64 }
The general shape of the C code is similar to the Java node earlier, except that the C code has more lower-level operations, such as the following:
Library inclusions for C (lines 3–6) and the Erlang interface (lines 8 and 9)
Allocation of memory for the input buffer (lines 11 and 18)
Freeing the storage allocated to Erlang terms in the C code (lines 53–56)
The node is set up and connected to an Erlang node in lines 24–33:
erl_init(NULL, 0)
initializes the erl_interface
library, and must be called
only once in any program.
erl_connect_init(1, "mycookie",
0)
initializes the connection mechanism, including the
identification number of the node (1
here) and the cookie that it is to
use.
fd =
erl_connect("blah@STC")
connects to the Erlang node blah@STC
and returns a file descriptor
fd
for the connection.
The loop
in lines 35–57 will loop
forever, reading a message (line 37) using the following line of
code:
got = erl_receive_msg(fd, buf, BUFSIZE, &emsg);
The preceding line of code will receive a message in the buffer
buf
and decode it into an Erlang term,
emsg
. ERL_TICK
messages that check whether the node is
alive are ignored (line 40), and the loop terminates on receiving an
ERL_ERROR
message (line 42). Otherwise,
the functional part of the loop
body
will do the following:
Extract the pid of the message sender, fromp
, and the payload, argp
(lines 46 and 47).
Convert the argp
into a C
integer, pass it to the factorial function, and return the Erlang term
{ok, fac(...(argp))}
to the
fromp
process (line 51).
The erl_format
call uses
a format string to construct Erlang terms in a readable way. To
construct the same term manually, you could write:
arr[0] = erl_mk_atom("ok"); arr[1] = erl_mk_integer(fac(ERL_INT_VALUE(argp))); resp = erl_mk_tuple(arr, 2);
Finally, the storage used for the Erlang terms and subterms is cleaned up (lines 54–56).
To compile this C program, you have to make sure the erl_interface.h file and the liberl_interface.a
and libei.a
libraries are used. You can do this (on
Mac OS X) using the following command:
gcc -o fac -I/usr/local/lib/erlang/lib/erl_interface-3.5.9
/include -L/usr/local/lib/erlang/lib/erl_interface-3.5.9
/lib fac.c -lerl_interface -lei
In the preceding code, the italicized path gives the path to the
latest version of erl_interface
on your
system. This compiles the C code as the executable fac
in the current directory.
The Erlang code to connect to the C node is given next. In general,
the name of the C node is cN
, where
N
is the identification number for the
node, so we use c1@STC
here:
-module(fac). -export([call/1]). call(X) -> {any, 'c1@STC2'} ! {self(), X}, receive {ok, Result} -> Result end.
Calling this in the Erlang shell gives the following:
% erl -sname "blah" -setcookie "mycookie" ...... at this point the C executable should be called ...... (blah@STC2)1>c(fac).
{ok,fac} (blah@STC2)2>fac:call(7).
5040 (blah@STC2)3>fac:call(0).
1
In the example, where the C node is acting as a client, the Erlang node needs to be launched first so that it is already running when the C node attempts to connect to it.
The C node you just saw is running as a client: it can make
connections to Erlang nodes. It can also run as a server mode; this
requires first that the program creates a socket—listening on a
particular port number—and then that it publishes the socket by means of
epmd
. This program can then accept
connections from Erlang nodes.
The erl_interface
library
provides various other facilities, such as pattern matching on incoming
messages, the registry system for storing key-value pairs, and a global
naming scheme. All of these, plus the server-style node, are covered in
the Interoperability Tutorial and in the user’s guide for the erl_interface
library.
One of the “hidden gems of OTP” is the erl_call
Unix command, built using erl_interface
to provide communication with a distributed Erlang node. As well as to
start and communicate with a node, you can use this to compile and
evaluate Erlang code from the command line. The ability to read from
stdin
allows other scripts to use this,
such as those in the CGI bin.
You present arguments and options to the command via a series of
flags. The full set is described in the manpage, or summarized by calling
erl_call
with no flags. One of the
flags –n
, -name
, or -sname
is required, as these flags are used to
specify the name (or short name) of the node to be called. Often, this is
accompanied by –s
, which will start the
node if it is not already running.
The –a
flag is the analog of
apply/3
, with arguments in a similar
format, whereas –e
evaluates what comes
from standard input (up to Ctrl-D
).
Here is the erl_call
command in
action:
%erl_call -s -a 'erlang date' -n blah
{2009, 3, 21} %erl_call -s -e -n blah
X=3, Y=4, X+Y. Ctrl-D {ok, 7} %erl_call -a 'erlang halt' -n blah
%
An Erlang port allows communication between an Erlang node and an external program through binary messages sent to and from an Erlang process running in the node—known as the connected process of the port—and the external program, running in a separate operating system thread (see Figure 16-1). The wxErlang binding to wxWidgets, described in Chapter 14, uses ports.
One of the simplest applications of a port is the os:cmd/1
function, which
can call an operating system command from inside the Erlang shell:
1> os:cmd("date").
"Sat 21 Mar 2009 18:11:24 GMT
"
The port is made to behave like an Erlang process that is not trapping exits. Connected processes can link to it, as well as send and receive Erlang messages and exit signals. The mechanism underlying the binary communication depends on the operating system: for instance, on Unix-based systems, communication will be through pipes. On the Erlang side, the template for a port-based interaction is given by the following:
Port = open_port({spawn, Cmd}, ...), ... port_command(Port, Payload), ... receive {Port, {data, Data}} -> ...
In this fragment, the port is opened by the call to open_port/2
, returning
the port identifier, Port
. Data is sent to the
Port
(and on to the external program)
through the call to port_command(Port,...)
in the connected process,
and data is received from the Port
in a receive
clause matching data of the form
{Port, {data, Data}}
.
We’ve just given you a top-level summary of how ports work. The remainder of this section looks in more detail at the Erlang commands that control ports, as well as the way data is coded and decoded for communication to the external program. Finally, we’ll show how you can write external programs in Ruby and C to communicate with Erlang through ports.
To open an Erlang port, you use the open_port/2
command:
open_port({spawn, Cmd}, Options)
This will run the command Cmd
as an external program; this external program is spawned with the given
list of Options
. Here is a list of
the main options available (you can find a complete list in the
documentation for the erlang
module):
{packet, N}
This gives the size of the binary packets to be used for
this port. N
can take the value
1, 2, or 4. Under this option, packets are preceded by their size.
If variable-sized packets are to be sent, you should use the
stream
option instead.
binary
All I/O from the port comprises binary data objects rather than bytes.
use_stdio
This uses the (Unix) standard input and output for
communication with the spawned process; to avoid this, the
nouse_stdio
option is
available.
exit_status
This ensures that a message is sent to the port when the external program exits; details are given in the online documentation.
For example, to run a Ruby program, echoFac.rb
, the following commands need to be
executed:
Cmd = "ruby echoFac.rb", Port = open_port({spawn, Cmd}, [{packet, 4}, use_stdio, exit_status, binary]),
After these commands are executed, the variable Port
contains the port identifier for the
spawned Ruby program.
The connected process can communicate with the Port
using port_command/2
. Executing the following
command in the connected process will send the Data
to the Port
:
port_command(Port, Data)
The message sent has the form {Port,
{data, Data}}
.
A port identifier such as Port
gives any Erlang process access to the
port, and thus to the external program attached to the port. Any
process can use this, but it is strongly recommended that instead of
direct communication using Port!...
, which fails if it is called from
any process other than the port owner, all communication
should use port_command/2
.
To connect a process with pid Pid
to a port Port
, the following call must be executed:
port_connect(Port, Pid)
This can be called by any process, but the old port owner will
stay linked to the Port
; the owner
will need to call unlink(Port)
to
remove the link.
To close a port—and therefore to terminate communication with the
external program—the connected process needs to execute the
command port_close(Port)
.
To show these examples in action, here is a small Erlang program
that calculates the factorial of 23 by sending the argument to Ruby and
having it calculated in the Ruby program echoFac.rb
, based on an echo example in the
erlectricity
library, discussed in
the section Working in Ruby: erlectricity.
In the following example, the communication primitives are highlighted:
-module(echoFac). -export([test/0]). test() -> Cmd = "ruby echoFac.rb", Port =open_port
({spawn, Cmd}, [{packet, 4}, use_stdio, exit_status, binary]), Payload = term_to_binary({fac, list_to_binary(integer_to_list(23))}),port_command
(Port, Payload), receive{Port, {data, Data}}
-> {result, Text} = binary_to_term(Data), Blah = binary_to_list(Text), io:format("~p~n", [Blah]) end.
Running the test/0
function
results in the following behavior:
1> echoFac:test().
"23!=25852016738884976640000"
ok
2>
We explain the remaining parts of the program in the next section.
Communication through a port uses binary data; therefore, program data
needs to be converted to binary in some way or another before
communication, and decoded on receipt. The bit syntax, described in
Chapter 9, can be used
here, as can a number of coding and decoding functions provided in the
erlang
module, including the following:
term_to_binary/1
This converts the argument to a binary data object that
encodes its argument according to the Erlang binary term format.
This can be communicated through the Port
if it is created with the binary
option.
binary_to_term/1
list_to_binary/1
This will return a binary that is composed of the integers and binaries in the argument. For example:
> list_to_binary([<<1,2,3>>,1,[2,3, <<4,5>>],4| <<6>>]). <<1,2,3,1,2,3,4,5,4,6>>
binary_to_list/1
Returning to the earlier example:
test() -> Cmd = "ruby echoFac.rb", Port = open_port({spawn, Cmd}, [{packet, 4}, use_stdio, exit_status, binary]), Payload = term_to_binary({fac, list_to_binary(integer_to_list(23))}), port_command(Port, Payload), receive {Port, {data, Data}} -> {result, Text} = binary_to_term(Data), Blah = binary_to_list(Text), io:format("~p~n", [Blah]) end.
The highlighted lines show the conversion in both directions. In
creating the Payload
, the integer
23
is converted to the string
"23"
by integer_to_list
, and then to a binary <<"23">>
, which in turn is paired
with the atom fac
and the pair is
coded as a binary.
In the receive
clause, the
message from the Port
is received in
the standard form, {Port, {data,
Data}}
. The Data
is decoded
to a term of the form {result, Text}
,
and then the Text
can itself be
decoded using binary_to_list
. This
allows the data to be output to the terminal.
As the example in the preceding section shows, it is pretty tedious to encode and decode the data yourself. Of course, you have the flexibility to choose efficient communication protocols between your external program and an Erlang node, but reinventing an encoding for each program is not the best way to do things.
If you do not want to go down that route, Erlang comes with libraries to support communication with the outside world, and particularly with C and Java. We covered the Java library at the beginning of the chapter; we’ll discuss interfacing with Ruby in the remainder of the chapter.
erlectricity
is a Ruby library—a “gem”—that you can download and use with
Ruby. It is available as source code from http://github.com/mojombo/erlectricity, and you can
install it in Ruby using:
$ gem install mojombo-erlectricity -s http://gems.github.com
You can include the erlectricity
library in a Ruby program by
using a require
statement at the head
of the file.
At the heart of the library is the receiver.rb
program, which provides the
functionality to allow messages to be received by and sent from a Ruby
program. This in turn depends on the implementation of ports in port.rb
and matching in matcher.rb
; coding and decoding data formats
are provided in encoder.rb
and
decoder.rb
for a variety of different
Erlang types.
The library comes with a test suite, as well as examples, including a daemon that links Erlang to Campfire, via the Ruby implementation of the Campfire API, Tinder.
The erlectricity
library for
Ruby provides support for communication with Erlang processes through
ports. Here is the Ruby side of the system, the echoFac.rb
program that communicates with
echoFac.erl
:
require 'rubygems' require 'erlectricity' require 'stringio' def fac n if (n<=0) then 1 else n*(fac (n-1)) end end receive do |f| f.when(:fac, String) do |text| n = text.to_i f.send!(:result, "#{n}!=#{(fac n)}") f.receive_loop end end
The working part of this program is the receive
method: on receipt of a message
f
this is matched with {fac, text}
, where text
is a String
. If this match is successful, the
text is converted to an integer (by calling the to_i
method on it) and the following message
is sent to the port from which the message came:
{:result ,"#{n}!=#{(fac n)}"}
In a Ruby string, the construct #{...}
surrounds an expression to be
evaluated. For example, if the variable n
has value 6
, then "#{n}!=#{(fac n)}"
will be the string
"6!=720"
.
As we said earlier, the result of running echoFac:test()
is:
1> echoFac:test().
"23!=25852016738884976640000"
ok
By default, an external program connecting to Erlang will run in a separate operating system process. This isolates the two parts of the system so that a crash in the external program will not affect the Erlang program, but it has the disadvantage of making it harder to meet certain lower-level, real-time requirements. To meet such requirements, it is possible to run an external program in the same thread as the Erlang system; this is called a linked-in driver.
Details of how to build linked-in drivers, including the callback functions that need to be implemented by such drivers, are provided in Chapter 6 of the online Interoperability Tutorial. Communication with linked-in drivers uses ports and the same BIFs as used in port programs.
When using linked-in drivers, your program will execute very, very
quickly. But this speed comes at a price, because when things go wrong,
they go very, very wrong. A crash or a memory leak in your linked-in
driver will result in the Erlang VM crashing. This is in contrast to
ports, where the port is closed and an EXIT
signal is sent to the connected
process.
Use linked-in drivers with extreme care, keep them simple, and
integrate them only when performance is critical—for example, when
integrating with an external system such as Berkeley DB, or integrating
a system call such as sendfile
in the
Yaws web server.
The Erlang Enhancement Proposal (EEP) process[39] is a mechanism for the Erlang community to propose enhancements to the language, and for the enhancements to be incorporated into the standard distribution. EEP7 is a proposal for a foreign function interface (FFI), which offers the promise of more reliable development of linked-in drivers. In the meantime, there are toolkits, such as the Erlang Driver Toolkit (EDTK) and Dryverl, which aim to support the development of linked-in drivers for Erlang.
Write an implementation of the earlier factorial server example in C using a port, rather than a distributed Erlang node.
Reimplement the factorial server example using the interface to one of the following languages: Scheme, Haskell, C#, Python, or PHP.
[36] This is done for the duplicate code detection algorithm in Wrangler, the Erlang refactoring tool. An existing efficient C library is used to identify candidate “clones” in Erlang software.
[37] If you’ve been programming Erlang for a few years and you
react at variables such as pid
and tuple
not being capitalized,
you are not alone. What is important is that in the process, you do
not openly question how methods taking atoms as parameters actually
work, ensuring that no one picks up on your blunder.
[38] The Turing test was proposed by mathematician and computing pioneer Alan Turing (1912–1954) as a test of machine intelligence. The idea, translated to modern technology, is that a tester chats with two “people” online, one human and one a machine: if the tester cannot reliably decide which is the human and which is the machine, the machine can be said to display intelligence.