Chapter 16. Interfacing Erlang with Other Programming Languages

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.

An Overview of Interworking

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.

Interworking with Java

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.

Nodes and Mailboxes

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();

Representing Erlang Types

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.

Communication

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 OtpErlangObjects 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.

Putting It Together: RPC Revisited

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
8       OtpNode bar = new OtpNode("bar");
9       OtpMbox 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 {
18           o    = 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);
26           mbox.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.

Interaction

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.

The Small Print

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

Taking It Further

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.

C Nodes

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

For printing error messages.

erl_global

For providing access to globally registered names.

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.

Going Further

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.

Erlang from the Unix Shell: erl_call

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
%

Port Programs

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
"
An Erlang port, its connected process, and an external program
Figure 16-1. An Erlang port, its connected process, and an external program

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.

Erlang Port Commands

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}}.

Warning

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.

Communicating Data to and from a Port

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

This is the inverse of term_to_binary/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

This is the inverse of list_to_binary/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.

Library Support for Communication

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.

Working in Ruby: erlectricity

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.

An example using erlectricity

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

Linked-in Drivers and the FFI

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.

Warning

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.

Exercises

Exercise 16-1: C Factorial via a Port

Write an implementation of the earlier factorial server example in C using a port, rather than a distributed Erlang node.

Exercise 16-2: Factorial Server in Another Language

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset