OCamlScript is a Javascript backend for the OCaml language which aims to provide a better language for the Javascript platform.
One OCaml module is mapped to one JS module, and no name mangling happens so that:
- The stacktrace is preserved, the generated code is debuggable.
- You can call
List.length
(List is a module in OCaml standard library) in a plain Javascript file.
let sum n =
let v = ref 0 in
for i = 0 to n do
v := !v + i
done;
!v
Generated code under is as below:
function sum(n) {
var v = 0;
for(var i = 0; i<= n; ++i){
v += i;
}
return v;
}
As you can see, there is no name mangling in the generated code, so suppose the
module is called M
, you can call M.fib
in vanilla Javascript
You can play the online in-browser compiler.
This project is currently released to exchange ideas outside Bloomberg and collect some early feedback from OCaml and Javascript community, it is in an very early stage and not production ready for your own projects yet.
Note that you have to clone this project with --recursive
option, we can only distribute
the patch of OCaml due to License restrictions.
- Apply the patch to OCaml compiler and build
Please ignore the warnings generated by git apply
cd ocaml
git apply ../js.diff
./configure -prefix `pwd`
make world.opt
make install
- Build OcamlScript Compiler
Assume that you have ocamlopt.opt in the PATH
cd ../jscomp
ocamlopt.opt -I +compiler-libs -I bin -c bin/compiler.mli bin/compiler.ml
ocamlopt.opt -g -linkall -o bin/ocamlscript -I +compiler-libs ocamlcommon.cmxa ocamlbytecomp.cmxa bin/compiler.cmx main.cmx
Now you have a binary called ocamlscript
under jscomp
directory,
put it in your PATH
- Build the runtime with
ocamlscript
cd runtime
make all
- Build the standard library with
ocamlscript
cd ../stdlib
make all
- Test
We first create a file called hello.ml
:
mkdir tmp # create tmp directory inside the stdlib
cd tmp
echo 'print_endline "hello world";;' >hello.ml
Then we compiled it with ocamlscript
OCAML_RAW_JS=1 ocamlscript -I . -I ../ -c hello.ml
It should generate a file called hello.js
, then we runt the js
file
node hello.js
If everything goes well, you will see hello world
on your screen.
The OCaml directory is a submodule from OCaml's official repo(4.02.3), all its rights are reserved by INRIA (see its QPL LICENSE for more details).
Our compiler relies on a patch (js.diff) to the OCaml compiler.
This project reused and adapted part of js_of_ocaml's:
- Some small printing utilties in pretty printer.
- Part of the Javascript runtime support
It adapted two modules [jscomp/lam_pass_exits.ml] and [jscomp/lam_pass_lets_dce] from OCaml's [bytecomp/simplif], the main reasons are those optimizations are not optimal for Javascript backend.
[jscomp/js_main.ml] is adapted from [driver/main], it is not actually used, since currently we make this JS backend as a plugin instead, but it shows that it is easy to assemble a whole compler using OCaml compiler libraries and upon that we can add more compilation flags for JS backend.
[jscomp/stdlib] is copied from [ocaml/stdlib] to have it compiled with the new JS compiler.
Since our work is derivative work, we choose the GPL v2 license to make it compatible with js_of_ocaml.
Note that QPL license is not compatible with GPL V2, so we distribute our changes to the compiler as a patch instead.
-
Readability
-
No name mangling
-
Support Javascript modules systems
-
Integrate with existing javascript ecosystem, for example, npm, webpack.
-
Straight-forward FFI, generate tds file to target Typescript for better tooling
-
Separate and extremely fast compilation.
-
Better performance than hand-written Javascript: thanks to a sound type system in OCaml so that we can play more optimizations and pipe it to Google Closure Compiler for production mode.
-
Smaller code than hand written JS code, compatible with Google Closure Compiler
-
Support NodeJs, Web Browser and various Javascript target platform.
-
Compatible with OCaml semantics modulo c-bindings and Obj, Marshal module
Below it is an idea how it would integrate with the existing JS eco-system:
var $$Array = require('./array'); // OCaml Array module
var List = require ('./list'); // OCaml List module
List.iter(function(x){console.log('hi, nodejs '+x)},
$$Array.to_list ($$Array.init(5,function(x){return x})))
You get the output:
hi, nodejs 0
hi, nodejs 1
hi, nodejs 2
hi, nodejs 3
hi, nodejs 4
Below is a contrived example to demonstrate our motivation, it tries to insert 1000,000 keys to an immutable map and query it
module IntMap = Map.Make(struct
type t = int
let compare (x : int) y = compare x y
end)
let test () =
let m = ref IntMap.empty in
let count = 1000000 in
for i = 0 to count do
m := IntMap.add i i !m
done;
for i = 0 to count do
ignore (IntMap.find i !m )
done
let () = test()
This is an equivalent JS version by using Facebook's immutable library
'use strict';
var Immutable = require('immutable');
var Map = Immutable.Map;
var m = new Map();
var test = function(){
var count = 1000000
for(var i = 0; i < count; ++i){
m = m.set(i, i );
}
for(var j = 0; j < count ; ++j){
m.get(j)
}
}
test ()
Runtime performance:
- OCaml Immutable Map: 1186ms
- Facebook Immutable Map: 3415ms
Code Size:
- OCaml (Prod mode): 899 Bytes
- Facebook Immutable : 55.3K Bytes
It covers the most of OCaml language, given that it is a quite young project (5 men-months until Jan 2016), there are still plenty of work to be done.
Some known issues are listed as below:
-
Language features:
Recursive modules, have not looked into it yet.
Better Currying support. Currently, we have an inference engine for function curring and we do cross module inference, however, there are some more challenging cases, for example, high order functions, it can be resolved by either aggressive inlining or fall back to a slow path using
Function.prototype.length
. We prepared the runtime support in module curry, will support it in the near future.Int32 operations, currently, Int use Float operations, this should be fixed in the near future.
-
Standard libraries distributed with OCaml:
IO support, we have very limited support for
Pervasives.print_endline
andPervasives.prerr_endline
, it's non-trivial to preserve the same semantics of IO between OCaml and NodeJS, one solution is to functorize all IO operations. Functors are then inlined so there will no be performance cost or code size penalty.Bigarray, Unix, Num, Int64
The OCaml to JS compiler libraries are distributed under the GPL (version 2.0); see the LICENSE file at the top of the source tree for more information.
The contents of some files in this distribution was derived from external sources with different licenses. The original copyright and license notice was preserved in the affected files.
If you have questions, comments, suggestions for improvement or any other inquiries regarding this project, feel free to open an issue in the issue tracker.