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':

externalconsoleLog : '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'.

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.