Thursday, December 11, 2008

Javascript talks Erlang

When extending my web-based IDE for X Erlang I stumbled on the fact that I am doing much the same thing over and over again adding a small snippets of code on the server and corresponding integration-only additions to Javascript on the client. Many times the obtained result would be an Erlang term represented by opaque string for the user (me) not the software to understand and interpret.

The next big step was standardizing the way Javascript (JSON) objects are generated from a subset of possible Erlang terms. For instance, [{name:"Judas"}, {occupation:"priest"}] would be converted to { name: 'Judas', occupation: 'priest' } on the Javascript side. So far so good, but the encoding scheme would work only in one direction - from Erlang-based server to the web browser.

The enligtment came later when I recognized that it is not at all necessary to add a new server stub for each request the web application may want to send. The web application may just call Erlang functions directly by their names, provide properly-encoded parameters and expect results as JSON messages. The question was the two-way encoding between Erlang terms and JSON objects. Here comes the scheme:

1. Erlang strings, including empty string "", are represented by Javascript strings

"" -> ""
abc" -> "abc"

2. Atom 'true' and 'false' become Javascript boolean values

'true' -> true
'false' -> false

3. Numbers are just numbers

123 -> 123
123.5 -> 123.5

4. Erlang atoms become objects with a single property "atom"

'abc' -> {atom: "abc"}

5. Erlang binaries become similar objects with a single property, this time named "binary"

<<1,2,255>> -> {binary: "0102ff"}

Binary data is encoded into string, each byte represented by two hex digits
(more compact representations can be imagined)

6. PIDs, References and Ports are encoded as objects too

#Ref<1.2.3> -> {reference: 2, node: 1, creation: 3}
#Port<1.2.3> -> {port: 2, node: 1, creation: 3}
<1.2.3> -> {pid: 2, node: 1, creation: 3}

7. Tuples become objects with integer field names

{T1,T2,T3} -> {1: T1, 2: T2, 3: T3}

8. Lists (which happened not to be strings) are the easiest

[T1,T2,T3] -> [T1, T2, T3]

The described encoding allows to represent any Erlang term as a Javascript value (but not vice-versa). The small open is funs. Today, they are encoded as {fun: name} but this representation is not reversible. Besides it dificult to understand how to reconstract fun object in Erlang (binary_to_term()?).

The additional sugar to the above scheme is record descriptions. For known records, the representation of tuples could be made simpler and more manageable. For instance, the record 'req' is known to have 'module', 'function' and 'args' fields. Then, the Erlang tuple {req,Mod,Fun,Args} is represented by more concise Javascript object with explicit field names: {record: "req", module: Mod, function: Fun, args: Args}. Record description work both ways, during encoding and decoding.

The implementation of the described representation allowed my dojo-based web client to call Erlang functions on the server and pass complex structures back and forth with little or no hassle.

Now, the Erlang console which evaluated expressions using erl_parse and erl_eval stores the resulting bindings on the client as Javascript objects and passes them to subsequent calls as a context not heeding their alien nature.

No comments: