Sep 6, 2017

Gradual Typing of JavaScript Using BuckleScript

USUALLY when we say 'gradual typing', we mean gradually adding type annotations to a dynamically typed codebase like JavaScript or Python, and then running it through a typechecker like Flow/TypeScript/mypy. However, an equivalent approach is to leave the existing codebase as it is, write typed bindings to it using the FFI capabilities of a statically-typed language, and use those typed bindings to interact with it.

This of course implies that we have no intention of retiring the original codebase; we simply want to interoperate with it as safely as possible. This is an especially ideal scenario when we're dealing with a massively successful and active body of code like that of the JavaScript community.

In fact, TypeScript and mypy themselves adopt this approach--using typed bindings. Their communities maintain massive libraries of library bindings ready for consumption. In this post, I'm going to demonstrate how BuckleScript actually allows us to gradually introduce type-safe bindings to existing JavaScript APIs.

console.log

We'll start with a simple example. From using JavaScript before, we will know that the built-in JavaScript Web API function console.log prints out an object that we give it on the browser's JavaScript log:

console.log(obj);

So in BuckleScript terms, we want to bind to a global value console.log that takes a single value and returns ... nothing. However, in BuckleScript (i.e., OCaml), all functions have to return some value. What do we return when we return nothing?

Well, since we're practising gradual typing, we won't worry about that right now, we'll just say that we return 'whatever':

external consoleLog : 'obj -> _ = "console.log" [@@bs.val]

According to the BuckleScript manual, the [@@bs.val] attribute binds a given name to a given global value. In this case, the value is the console.log function. The 'obj type means 'any type, let's call it "obj" ', and the _ 'wildcard' type means 'I don't care what the type is, let the typechecker infer it'.

If you bind and then call this function in the BuckleScript playground:

external consoleLog : 'obj -> _ = "console.log" [@@bs.val]

let () = consoleLog "Hello, World!"

You get the following JavaScript output:

// Generated by BUCKLESCRIPT VERSION 1.9.2, PLEASE EDIT WITH CARE
'use strict';


console.log("a");

/*  Not a pure module */

So, a binding with almost no concrete type information (except that it's a function of one argument) compiles down to JavaScript that just works! Admittedly, console.log is a simple function which accepts pretty much any input. In the next installment (this one is getting a little long) I'll cover another slightly more complex function and show how we can gradually type it in BuckleScript.

No comments: