Showing posts with label ocaml. Show all posts
Showing posts with label ocaml. Show all posts

Sep 11, 2017

BuckleScript Gradual Typing: Incremental Change

IN THE previous post, I showed a basic example of overlaying an OCaml type over a dynamically-typed JavaScript function, using BuckleScript's FFI feature. In this post, I will show a slightly more complex example: binding to the Web API's Intl.DateTimeFormat constructor. As usual, you can follow along by typing and pasting code into the BuckleScript playground.

In JavaScript, we would use the constructor like:

const format =
  new Intl.DateTimeFormat("en-CA", { hour12: false });

We do need to note, though, that both of the arguments to Intl.DateTimeFormat are optional, and will be given default values if they are not passed in. So really in our BuckleScript binding we want to somehow make them optional as well.

Fortunately, OCaml and BuckleScript allow for optional function parameters, as long as we make the final parameter non-optional:

type dateTimeFormat

external dateTimeFormat_make :
  ?locales:'locales -> ?options:'options -> unit -> dateTimeFormat =
  "Intl.DateTimeFormat" [@@bs.new]

let format =
  dateTimeFormat_make
    ~locales:"en-CA" ~options:[%obj { hour12 = false }] ()

We introduce several new concepts here:

type dateTimeFormat is an abstract type that, for now, is defined only by the fact that we create values of the type with:

external dateTimeFormat_make ... is a binding to a constructor (thanks to the [@@bs.new] attribute) which takes optional locales and options arguments, a required unit argument, and returns an instance of dateTimeFormat. The optional parameters are marked by the '?' symbol.

let format = ... is a usage of the dateTimeFormat_make FFI binding to force BuckleScript to actually generate a call to the Intl.DateTimeFormat constructor. By default BuckleScript only generates JavaScript output lazily.

In the spirit of gradual typing, I've also been lazy about working out the actual types of the parameters to the dateTimeFormat_make binding. Partly it's because the free type variables 'locales and 'options allow me to pass in any type of argument into those slots. In the previous snippet, I passed in the correct types (a string and a JavaScript object with a valid keyword) intentionally, but there is no type-level guarantee that I have to do so. I could have passed in values of invalid types, and BuckleScript would merrily generate JavaScript which would only fail at runtime.

So how can I force some more measure of type safety into the bindings here? Well, for the locales parameter, the Web API reference for Intl.DateTimeFormat says its type has to be either a string or an array of strings. In BuckleScript, we can model this 'either-or' scenario using the [@bs.unwrap] attribute:

external dateTimeFormat_make :
  ?locales:([ `one of string | `many of string array ] [@bs.unwrap]) ->
  ?options:'options ->
  unit ->
  dateTimeFormat =
  "Intl.DateTimeFormat" [@@bs.new]

let formatOne = dateTimeFormat_make ~locales:(`one "en-CA") ()

let formatMany = dateTimeFormat_make ~locales:(`many [|"en-US"; "en-CA"|]) ()

Now, we can pass locales only as a single string or as an array of strings. Anything else is a type error. The compiler does need some help though, to statically tell the difference; hence we have to use polymorphic variants to differentiate one case from the other.

Note that I left out the options argument in these cases to make them simpler. If you paste this into the playground, you'll see BuckleScript generates undefined arguments in their place.

This post is getting rather long now, so I'll explore how to make the options argument more type-safe in the next one. It's going to be quite fun, because there's quite a lot we can do in BuckleScript to enforce the business rules of the API at the type level!

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.

Jan 21, 2017

BuckleScript: a significant new OCaml to JavaScript compiler

RECENTLY I've been paying attention to the BuckleScript compiler from OCaml to JavaScript (ES5, to be exact). I'm seeing some significant advances that it brings to the state of the art, and thought I would share.

A little background: I use Scala.js at work, and it is rock-solid. Once you're set up with a project, you are pretty much good to go. Bindings to significant JS libraries are high-quality; I use some of the biggest names in JS frontend dev libraries today, and all in a nice Scala-style object-functional layer.

That aside, I still periodically look around the ecosystem and try to evaluate the latest developments. I've been hearing about BuckleScript for a while, but only recently decided to try it out for a side project after trying and failing to understand how this works in JavaScript.

So, without further ceremony, let me present my findings.

BuckleScript's compiler is insanely fast, because Bob Zhang (the project lead) has taken to heart the OCaml obsession with performance and routinely tries to optimise away millisecond-level delays. Once you get a taste of that speed (entire project compiled faster than you can blink), you'll find it difficult to go back to something slower. It's like getting to use git after you've used svn all your life.

It compiles to idiomatic, readable ES5, with nice indentation, (almost) no name mangling, and a one-to-one mapping from OCaml modules to ES modules (whichever kind you prefer: Require, AMD, Google).

It targets and integrates with the existing npm ecosystem; it doesn't try to introduce yet another package manager. It makes writing bindings (types) for existing JS libraries reasonably easy, and the documentation (the manual especially) is fantastic at guiding you through that.

OCaml is a bit of an odd duck syntax-wise, even among the functional programming languages. There are nuances to get used to. But once you get used to them, it is a pleasure to program in. And if you just can't get used to them, you can always try out Facebook's Reason, which is an alternative, JavaScript-lookalike syntax for OCaml.

This focus on integration and ease of reuse of the JavaScript ecosystem means it's feasible to leverage the npm package collection in your pure OCaml project. You can deploy a backend server which performs core functions as a statically compiled, native binary (i.e. not nodejs); deploy ES5 nodejs services which take advantage of specialised npm packages for MSSQL querying, or SOAP clients, or what have you; and you can deploy ES5 in your frontend webapp scripts, all written in pure OCaml.

So, why OCaml specifically? After all, there are plenty of nice languages out there.

As it turns out, that OCaml obsession with speed and type-safety together serve it well here. It's a pragmatic, simple, and matter-of-fact language, and its runtime model maps very well to the JavaScript runtime model, while also preserving important compile-time safety guarantees.

I should emphasise that it's pragmatic: you're not forced to deal with the added mental load of monads and other type system rabbit holes--but they're all available if you need them! Personally, I feel that laziness, purity, and monads have driven away more people than they've attracted. I think that OCaml gets the balance right. Others obviously feel differently. But in concrete terms, BuckleScript is a significant contribution that shouldn't be missed.

If you've developed in a compiled language for any length of time and like type-safety guarantees, after trying BuckleScript you'll be asking yourself how much time you've wasted over the years waiting for your compiler to finish so you can continue your edit-compile cycle. Maybe it's best not to think too much about that.