MiXture -- Bridging F# and JavaScript

In this blog post I will introduce MiXture, a project I made to bridge F# and JavaScript. MiXture allows F# code to call JavaScript and vice versa. MiXture uses V8, Google’s JavaScript engine, to execute JS code. MiXture is offered in the form of a DLL, that should work on all platforms executing .NET; however I’ve only tested this on Mono v2.10.9 for Mac, and there are high chances it won’t work out-of-the-box for other platforms.

Where MiXture came from

The implementation of MiXture is based on J.B. Matthews’ Ph.D. dissertation, where he describes the semantics of joining two languages. In his dissertation, he introduces two calculi (for an ML-like and a Scheme-like languages) to formally describe two theories for language interoperability. I won’t go into the details of his dissertation, and in fact I will only deal with the most integrated kind of interoperability: the natural embedding.

The natural embedding translates values of one language into the other. For MiXture, this is a two-way translation capability for JavaScript and F#. This is not always possible, as there might not be an exact equivalent representation in the other language (e.g., JavaScript does not have integer type numbers).

Other JavaScript bridges

While I started this project mainly from reading J.B. Matthews’ dissertation, I also encountered when I was handing in my dissertation that similar projects are out there in the wild. For instance, Apple pushed the first commit a year ago for an Objective-C — JavaScript bridge. I was quite surprised when I saw that Apple’s library coincidentally shares some of the names in the API (e.g., JSValue, JSContext). I’m glad I saw this as it validates this project is not just done from the theoretical point of view I was coming from, but also has some real-word usages :).

Check out an introduction to Apple’s library here and here.

Overview of the MiXture API

Let’s take a look at the API MiXture exposes to F# developers wanting to create applications using both languages.

JSContext

A JSContext is an execution environment that allows seperate, independent JavaScript applications to run in MiXture. JSContext is a wrapper for V8 contexts.

There are some functions in the module JSUtils to manipulate JSContexts, such as create_context: unit -> JSValue, set_current_context: JSContext -> unit and register_values: (string * JSValue) list -> unit.

JSValue

The JSValue in F# represents native JavaScript values in a specific JSContext seen in F#.

embed and project

The two main functions to work with MiXture are embed and project, which use values of the type JSValue. The JSValue in F# represents native JavaScript values seen in F#. embed converts any F# value into a JSValue and project performs the opposite operation. The type someone could expect for embed would be 'a -> JSValue, however, the type is obj -> JSValue as embed does not behave polymorphically (it inspects the type of its argument in order to dispatch it to the appropriate converting function).

For the specific case in which we are embedding a polymorphic function, we must use the function embed_poly_func, as we can only get the polymorphic type of a value using quotations. We’ll see an example of this in a code sample below.

Code samples

Let’s move to the fun part, some examples that show off some of the features of MiXture:

  • Here, we project a JavaScript function and see that MiXture supports currying:
1
2
3
4
5
6
7
8
9
10
11
12
13
// surround_str is an F# curried function
> let surround_str: string -> string -> string -> string =
    "(function(beginning, end, str) { return beginning + str + end; })"
    |> JSUtils.execute_string
    | project;;
val surround_str : string -> string -> string

> let angle_surround = surround_str "<" ">";;
val angle_surround : string -> string

> printfn "%s" <| angle_surround "Hello, world!"
// ==> "<Hello, world!>"
val it : unit = ()
  • In this other example, we show that MiXture also supports polymorphism from JavaScript to F#…
1
2
3
4
5
6
7
8
9
10
11
> let js_rev<'a> : 'a list -> 'a list =
    "Array.reverse"
    |> JSUtils.execute_string
    |> project
val js_rev<'a> : ('a list -> 'a list)

> js_rev [3.14; 2.71; 1.68]
val it: float list = [1.68; 2.71; 3.14]

> js_rev ["Life"; "is"; "simple"; "it's"; "just"; "not"; "easy"]
val it : string list = ["easy"; "not"; "just"; "it's"; "simple"; "is"; "Life"]
  • … and from F# to JavaScript:
1
2
3
4
5
6
> let js_poly_append = embed_poly_func <@ Array.append @>;;
val js_poly_append : JSValue

// register the value js_poly_append as "poly_append" in the current JavaScript context
> register_values ["poly_append", js_poly_append];;
val it : unit = ()

We can see the use of embed_poly_func here to embed a polymorphic function. poly_append is now registered in the current JSContext, and we could execute JavaScript code by passing it to the function JSUtils.execute_string. We show it as if it were an interactive JavaScript console:

JavaScript using poly_append; the lines beginning with “>” would be passed to JSUtils.execute_string
1
2
3
4
5
6
> var append_naturals = poly_append([0,1,2])
function () { [native code] }
> append_naturals([3,4,5])
0,1,2,3,4,5
> append_naturals(["hello", "world"])
Error: An F# exception occurred. Types not compatible

We see that even JavaScript is not type-safe, it is not allowed to pass the incorrect types to poly_append.

Free memory management

Another highlight is that MiXture doesn’t require manual memory management as it uses F#’s and V8’s garbage collectors.

Consider the case in which F# has a JSValue that is a V8 persistent handle. When this JSValue comes out of scope, MiXture automatically calls Persistent::MakeWeak on the V8 handle to trigger a callback from the V8’s garbage collector.

Type equivalences

Here’s a table of type equivalences for MiXture:


If you are interested in knowing more, here’s my dissertation that goes into all the gory details.

You can also check out the source on GitHub.

Comments