Bucklescript

What is Bucklescript?

Bucklescript is an OCaml to Javascript transpilier - it allows you to “write JS faster, safer and smaller”.

Why use Bucklescript, instead of other options like say, Elm?

Due to the huge ecosystem behind it, OCaml, opam, and a few large companies (Bloomberg, Facebook), it will have staying power the likes of which those newer languages have trouble with. It has the full power of the OCaml language, of which can be used with a simplified syntax almost identical to that of Elm to far more that will not restrict you in getting actual work done. Combined with its ability to call Javascript and vice-versa in clean ways while generating clean and readable and efficient code that is fully typed will make working on the front-end significantly better, easier, and more maintainable.

Have a look at this thread.

Where can I see some examples of it?

Here: Testing :smiley:


Here is the original post of this thread before it was made into a wiki - it goes into more detail:

So I’d mentioned before that I’ve wanted a fully typed language to use instead of javascript, and I picked Elm because it was the most ‘complete’, although Purescript looks better from an implementation perspective, well as of a few days ago another language appeared as version 1.0 (1.0.1 at the time of this post) that compiles the entirety of OCaml in such a way that makes it interoperable with Javascript, both ways by using an OCamls FFI syntax, it is Bucklescript.

OCaml is an ancient ML derivative that is ‘object’ based, so its fits in well with Javascript.

A couple of code examples (mostly from their docs) for those of you, who like me, like seeing how a language is by its code:

##Hello World

OCaml source:

print_endline "Hello BuckleScript!"

Output javascript:

// Generated by BUCKLESCRIPT VERSION 1.0.1 , PLEASE EDIT WITH CARE
'use strict';
console.log("Hello BuckleScript!");
/*  Not a pure module */

First thing to notice is that it really does output pure javascript, no including of 600kb+ of ‘standard unused library’ like with Elm, and there are sensible conversions from standard OCaml calls to javascript. Also note a comment at the end saying whether it is or is not a pure module, good documentation.

Function definitions

OCaml source:

let test0 =
  42
let test1 i =
  i + test0
let test2 =
  test1 18
let test3 f i =
  f i
let test4inc i =
  test3 test1
let test4 i =
  test3 test1 i
let test4explicitlytyped : int -> int =
  fun i -> test3 test1 i
let test4inctyped : ( int -> int ) -> ( int -> int ) =
  fun i -> test3 test1
let test5 f =
  f##hi 1
let test6 f   =
  let u = f##hi in
  u 1
let test7 f =
  let u = f##hi in
  u 1 [@bs]

Output javascript:

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

var Curry = require("stdlib/curry");

function test1(i) {
  return i + 42 | 0;
}

var test2 = 60;

function test3(f, i) {
  return Curry._1(f, i);
}

function test4inc() {
  return function (param) {
    return param + 42 | 0;
  };
}

function test4(i) {
  return i + 42 | 0;
}

function test4explicitlytyped(i) {
  return i + 42 | 0;
}

function test4inctyped() {
  return function (param) {
    return param + 42 | 0;
  };
}

function test5(f) {
  return f.hi(1);
}

function test6(f) {
  var u = f.hi;
  return Curry._1(u, 1);
}

function test7(f) {
  var u = f.hi;
  return u(1);
}

var test0 = 42;

exports.test0                = test0;
exports.test1                = test1;
exports.test2                = test2;
exports.test3                = test3;
exports.test4inc             = test4inc;
exports.test4                = test4;
exports.test4explicitlytyped = test4explicitlytyped;
exports.test4inctyped        = test4inctyped;
exports.test5                = test5;
exports.test6                = test6;
exports.test7                = test7;
/* No side effect */

Quite a lot of things going on here. First I notice that it states the module is side effect free, which is great. Second I notice the exports, meaning this can be put in as an ES6 module (and indeed the compiler does output to the module format, a module per ocaml module, 1-to-1 mapping), and it exports all the functions that I left externally visible, with the same names.

I tested a lot of different simple styles of functions, ones that just return a value are not functions but rather constants, and that is indeed reflected in the output, as can be seen in test0, it also optimizes down a path so test2 becomes constant too. Interestingly test1 takes an integer and returns an integer, but since it is a publicly exported function (I tested private functions and it did not do this) it makes certain it always returns an integer, always, by returning 0 if the i + 42 happened to fail for some reason (like i not being a number in javascript), I like this design decision (I’m curious if it is possible to change the default, a wrapper function could for sure at least).

I then tested some currying, a plain curry in test3 involves a library call (an stdlib/curry javascript module is output), the reason for this is in case the passed in function took more than one argument, so it does not know if it is returning some value or returning another function to be curried further. If the code was pure ocaml calls then it can optimize that out, but for javascript integration it leaves this little function call here for them. In test4 I test calling test3 with a function and do indeed see the Curry._1 call gets optimized out. I was curious though and made a test4inc that only partially applied the function, and fascinatingly I find it also optimized it out. I also made a test4explicitlytyped function that was completely typed so I could see how that changed, no changes from test4 so the default value was still left there for javascript purposes. Typing the curried version had no change either.

I then tried out some of the new special constructs that Bucklescript adds to OCaml, starting with ##, which becomes the field get operator in javascript (I.E. .). test5 was the most simple test of that, easily converting f##hi 1 into f.hi(1). I then curry that to see how it acts, and it indeed puts in the Curry._1 call again, however it has some attributes that you can add to expressions to do various things, one of those is adding [@bs] to the call to tell it to fully ‘resolve’ the function call, in other words no currying, it will fully call it. This attribute is mostly for javascript compat as normal ocaml code will resolve it out properly in most cases.

Matching

OCaml source:

let matching v = match v with 
     (true,true)   -> true
   | (true,false)  -> false
   | (false,true)  -> true
   | (false,false) -> true;;

Output javascript:

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


function matching(v) {
  if (v[0] !== 0 && v[1] === 0) {
    return /* false */0;
  }
  else {
    return /* true */1;
  }
}

exports.matching = matching;
/* No side effect */

Seems quite correct.

OCaml class

OCaml source:

class ['a] stack =
    object (self)
      val mutable list = ( [] : 'a list )  (* instance variable *)
      method push x =                      (* push method *)
        list <- x :: list
      method pop =                         (* pop method *)
        let result = List.hd list in
        list <- List.tl list;
        result
      method peek =                        (* peek method *)
        List.hd list
      method size =                        (* size method *)
        List.length list
    end

Output javascript:

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

var CamlinternalOO = require("stdlib/camlinternalOO");
var List           = require("stdlib/list");

function stack_init($$class) {
  var ids = CamlinternalOO.new_methods_variables($$class, [
        "size",
        "push",
        "pop",
        "peek"
      ], ["list"]);
  var size = ids[0];
  var push = ids[1];
  var pop = ids[2];
  var peek = ids[3];
  var list = ids[4];
  CamlinternalOO.set_methods($$class, /* array */[
        push,
        function (self$neg9, x) {
          self$neg9[list] = /* :: */[
            x,
            self$neg9[list]
          ];
          return /* () */0;
        },
        pop,
        function (self$neg9) {
          var result = List.hd(self$neg9[list]);
          self$neg9[list] = List.tl(self$neg9[list]);
          return result;
        },
        peek,
        6,
        List.hd,
        list,
        size,
        6,
        List.length,
        list
      ]);
  return function (_, self) {
    var self$1 = CamlinternalOO.create_object_opt(self, $$class);
    self$1[list] = /* [] */0;
    return self$1;
  };
}

var stack = CamlinternalOO.make_class([
      "peek",
      "push",
      "size",
      "pop"
    ], stack_init);

exports.stack = stack;
/* stack Not a pure module */

It seems to make a proper javascript class, returning javascript object constructor that makes what you would expect

A Map

OCaml source:

module StringMap = Map.Make(String)

let stringMap =
  let map = StringMap.empty
  in StringMap.add "aKey" "someValue" map

Output javascript:

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

var $$Map    = require("stdlib/map");
var Curry    = require("stdlib/curry");
var $$String = require("stdlib/string");

var StringMap = $$Map.Make([$$String.compare]);

var stringMap = Curry._3(StringMap[/* add */3], "aKey", "someValue", StringMap[/* empty */0]);

exports.StringMap = StringMap;
exports.stringMap = stringMap;
/* StringMap Not a pure module */

So it has its own map module with its interface, and it does appear to be immutable as it should be, nice.

List and list operation

OCaml source:

let multiply n list =
  let f x = n * x
  in List.map f list

let testing0 l =
  multiply 42 l

let testing1 =
  testing0 [ 1; 2; 3 ]

Output javascript:

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

var Caml_int32 = require("stdlib/caml_int32");
var List       = require("stdlib/list");

function multiply(n, list) {
  var f = function (x) {
    return Caml_int32.imul(n, x);
  };
  return List.map(f, list);
}

function testing0(l) {
  return multiply(42, l);
}

var testing1 = multiply(42, /* :: */[
      1,
      /* :: */[
        2,
        /* :: */[
          3,
          /* [] */0
        ]
      ]
    ]);

exports.multiply = multiply;
exports.testing0 = testing0;
exports.testing1 = testing1;
/* testing1 Not a pure module */

Got more ocaml modules here, everything is expected and it is interesting to see that it uses Cons cells for lists in javascript too.

Calling a javascript thing

OCaml source:

external create_date : unit -> 't = "Date" [@@bs.new]
let date = create_date ()

Output javascript:

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


var date = new Date();

exports.date = date;
/* date Not a pure module */

Other than the horrible syntax coloring here it all looks perfect.

I’m still unsure which has the better syntax as of yet, Bucklescript or Purescript, but Bucklescript has the entire power of ocaml and its libraries (that do not include native calls) at its disposal while being able to compile down to javascript or native (via llvm) code, and it has an in-browser compiler that you can include if you want anything dynamic. (See it in use here).

Purescript, however, looks to have a better syntax, removing old warts and such, however it has no build system that I was able to find for brunch and it has a large lack of libraries still.

At this point I am leaning toward Bucklescript. It would actually be pretty easy to take a virtualdom and let Bucklescript use it while also optimizing the virtualdom to be able to make a lot of expectations of immutability in its structure to gain the same speed that Elm has. It is very new too still, thus lacking a lot of ‘specific’ libraries that will be useful later, as well as missing some use-case specific tooling, although it already has all of ocaml’s extensive tooling.

This looks like it will be quite interesting though and I will be watching it. :slight_smile:

20 Likes

With so many options, including Facebook’s reason, it’s difficult to choose. I’m wondering how long will these solutions be maintained, and (hypothetically) what will happen if Bloomberg or the main developer(s) were to stop supporting BuckleScript?

Also, how would you use reactjs with bucklescript?

edit: forgot to say, thank you for the explanation and description of bucklescript! :slight_smile:

2 Likes

As I understood - you do not actually have to choose between Reason and Bucklescript. You can compile Reason to OCaml’s AST and translate it to javascript with Bucklescript.

2 Likes

This makes sense as I’ve heard people make the analogy between Reason/OCaml and Elixir/Erlang.

2 Likes

If it compiles to OCaml without native-code modules then Bucklescript can convert it to either javascript or native (via LLVM). Most of Bucklescript is written in Bucklescript now and they are working on finishing the rest so it is entirely self-hosted. The compiler can be compiled to javascript as it is (great for the code-pen) or to a native application (for speed, although it is already fast).

I still hope for an Elixir layer on top of mlfe for easy compat between them. I so very much love typed languages. ^.^

Fairly trivially actually. Bucklescript converts ocaml into the same javascript so you could call it from JS, or you could define an FFI for the ReactJS calls and classes (since OCaml is class-based ml it is a direct mapping between the two) and use it directly. It is perfectly interoperable with javascript both ways, you just have to ‘type’ the stuff similar to TypeScript except ocaml also supports doing awesome stuff like (from the examples), say you have a javascript function called add, which can take anything that you can + together, like numbers or strings or so, and you want to import it as a ‘generic type’ into ocaml/bucklescript for use:

OCaml code:

type _ kind =
  | Float : float kind
  | String : string kind
external add : ('a kind [@bs.ignore]) -> 'a -> 'a -> 'a = "" [@@bs.val]

let () =
  Js.log (add Float 3.0 2.0);
  Js.log (add String "x" "y");

Javascript result of the two ‘log’ lines (the external declaration is purely for the type system so it generates no code):

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


console.log(add(3.0, 2.0));

console.log(add("x", "y"));

/*  Not a pure module */

With the [@bs.ignore] attribute you can easily define an argument that is not passed to javascript, hence ignored, and in this case we are using it purely for its ‘type’, so we can type the rest of the arguments. The rest of the arguments, as well as the return type, are all 'a too, the same as the first ignored type that we got the kind of. Thus we can call the javascript add function with any type by specifying that type first.

You could of course just always make new names for it based on the type too, like addFloat and addString or whatever too. :slight_smile:

3 Likes

Sort of in the same boat… here
In my opinion Elm’s runtime is quite a package for say some JavaScript for a mobile device.
So I’ve also been looking at alternatives. Other than Purescript and BuckleScript(Ocaml/Reason) I also considered Fable an F# to JS compiler. But the latter also comes with a runtime although I believe smaller than Elm’s.
I’ve chosen Purescript because it’s the only one without a runtime.
I must say that even after a lot of Elm, I still experienced quite a bump in getting production ready with it.
Anyway… the reason I responding is

however it has no build system that I was able to find for brunch

I can see that’s a concern. But must say that I find the Phoenix/Brunch combo a PIA.
Not only because of NPM related issues but because all these project have great compilers which offer a lot of feedback.
For me it’s counter productive to see this feedback surrounded by the stuff Phoenix and the others processed add to it.
For Elm development I started using ex_guard. It watches my elm directory and signals the Elm compiler.
Which creates a bundle than can be picked up by Brunch or Webpack or…? This is only faster it also can be used in isolation so I can focus on the compiler feedback…

2 Likes

PureScript also does dead code elimination! It’s a great underrated language.

As for your second paragraph—I agree. If i’m going to have a JS/compile to JS heavy frontend, I’ll just make the Phoenix part only spit out JSON or like you said use other tools. I doubt the team ta DockYard (where Chris works) puts their whole Ember apps inside their Phoenix directories.

2 Likes

I ‘almost’ chose Purescript a few months back over Elm for the same reason. Some days I wish I did. The way Elm structures its code makes it amazingly difficult for the google closure compiler to shrink it. If Elm broke up its internal ‘modules’ into javascript modules then it would be able to do a fantastically better job at slimming it down. Bucklescript does that for example.

I’d love an example on that? I tried switching to webpack as a test in the beginning, that was horrible. Build times skyrocketed and everything was significantly more wordy. With brunch I’ve brought in a few dozen packages, no conflicts or issues with any, able to tell it to link in only what I want and where I want it, but the main reason is that it is blazing fast, it recompiles as fast as I hit Save.

Everything I’ve seen other than Purescript have brunch compilers, from Elm to React and JSX to SASS to etc… etc… To me it just says that Purescript is young and not well used yet if it does not have a compiler for the oldest and fastest JS build engine.

All I do is hit ‘Save’ when editing an elm file, brunch detects it, recompiles the elm to JS, then compiles all the JS together, very quickly (the Elm compiler itself is the slowest part). I did not need to build any watcher, do not need to signal anything, etc… And I can still elm make if I want too. I also get the build output in my editor (atom) with things I can click on to jump to lines, do auto-corrections that make sense (like adding types), etc… If I have to futz with a build system ever, then I consider the build system broken (old C++ holdover, I hate build systems with a passion more than I hate untyped languages).

2 Likes

After spending some time with it I’m really starting to like PureScript.
But as a whole it’s not a polished as Elm and it also doesn’t seem to have as much traction as Elm or Elixir.

There is a Erlang backend BTW. Early stage but might be nice in combination with Elixir

Anyway thanks for recognising my point…

4 Likes

The sad reality is that PureScript, by emulating Haskell as much as possible, made itself unlikely to ever be popular (especially with front end developers). Elm’s current goal is to be as user and beginner friendly as possible, which is fair. And to be fair, outside of a few forums Elm has little traction and most of it is hype. I consider “traction” when more than a few established companies actually use it. Most PureScript users are quiet and don’t seem to blog much, but the language has had a couple of conferences and is fairly popular with old school functional programmers (not a lot of people).

As I mentioned, if you have separate projects for backend and frontend, then you can use PureScript with no problem. You just make HTTP requests to your Phoenix backend and get the JSON back. If you want to keep everything in a single project though, then yes you’ll have to find a build tool that works. I’m personally used to having separate projects unless the app is mostly static and server-rendered templates.

I’ve found PureScript awesome to learn more interesting typed functional programming techniques while still being a language I can kinda make stuff with.

3 Likes

Lol, that is entirely awesome. I wonder how it would compare with mlfe. Sadly the Erlang system does not handle currying efficiently or could bake it into a sublanguage like how purescript is doing. I wonder how well that style works, seems similar to that one defcurry function that an elixir package has that does the same.

Yeah I quite like the look of it as well. Gone a bit through the tutorials but not written anything in it. I really like OCaml so I have hopes for Bucklescript, but only time will say…

2 Likes

My experience with Bruch was not so nice…
But I was just trying to bring up that using elm-make, psc or bsc by itself is doable without including Brunch.
If yours setup is working for you I definitely wouldn’t change it…

I’ve seen claims about Bucklescripts dead code removal.
But never got it to work myself. The console.log example is small, but it soon started to grow as I included more stuff. Do have a setup that produces a minimal (production) output…?

2 Likes

Ah yes that is entirely doable, and I did have a few things on their own where I call the compilers themselves until I ended up removing them (and making a brunch plugin in one case). Just those methods are painful when I want it to ‘just work’, that is something Elm has going for it. >.>

I went through lots of tests and it did that perfectly every single time. Anything exposed publicly to the module and anything those reference were optimized very well and set as javascript. Anything not exposed, helpers, etc… were not exposed. Do you have an example that can be pasted into Redirecting to bucklescript.github.io/bucklescript for an example case where the ocaml compiler does not do that? Do recall that it is not bucklescript but rather the ocaml compiler itself that is doing that; it is an optimizing compiler that bucklescript gets the output of so if you have a case where it is not doing that then you found a bug in the official ocaml compiler itself so that should definitely be reported.

1 Like

Yeah I’ve not been able to have the compiler keep unused private code yet. An example:

Ocaml:

module Hello : sig (* This is a submodule, only one thing is public in it *)
 val hello : unit
end = 
struct
  let im_unused = "world"
  let message = "Hello"
  let hello = print_endline message
end

(* Module Public. *)
let hello =
  Hello.hello

Javascript:

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


var hello = (console.log("Hello"), /* () */0);

var Hello = /* module */[/* hello */hello];

exports.Hello = Hello;
exports.hello = hello;
/* hello Not a pure module */

Er, that optimized it too much so not a great example, let’s add more:

Ocaml:

module Hello : sig (* This is a submodule, only one thing is public in it *)
 val hello : unit
end = 
struct
  let im_unused = "world"
  let message = "Hello"
  let hello = print_endline message
end

(* Module Public. *)
let hello =
  Hello.hello; 42

Javascript:

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


var hello = (console.log("Hello"), /* () */0);

var Hello = /* module */[/* hello */hello];

var hello$1 = 42;

exports.Hello = Hello;
exports.hello = hello$1;
/* hello Not a pure module */

It only keeps code that is referenced and only exports to javascript those that are exported via the ocaml module, in this case the hello function and the Hello submodule (that in turn has a Hello.hello method public, the other two are hidden, one of which entirely vanished and the other got optimized ‘into’ the only place it was used).

If you have an example where it is not performing dead-code elimination or optimizing properly then I would be highly curious as that would be a core ocaml compiler bug. :slight_smile:

1 Like

I trust the Ocaml compiler fully. And it’s not an compiler issue.

I was referring to the Immutable Data Structures example

It claims to be 899 Bytes which is really tiny and I found hard to believe when I first heard of OcamlScript/BuckleScript.

When I put the example in the play ground it adds

var Caml_obj = require("stdlib/caml_obj");
var $$Map    = require("stdlib/map");
var Curry    = require("stdlib/curry");

Which certainly makes it bigger.
I just don’t know what the actual size is going to be.
And don’t know what to expect from a production build…?

1 Like

There is also https://facebook.github.io/reason/ for ocaml users :slight_smile:

Nice podcast
Javascript Air podcast - Typed JavaScript with TypeScript and Flow

That podcast makes me reaaaaally want to use TypeScript. Its creator is so enthusiastic about it!

Ah yes it does, however it prunes those pretty well too from my testing. :-)[quote=“mkunikow, post:16, topic:1579”]
There is also https://facebook.github.io/reason/ for ocaml users :slight_smile:
[/quote]
Actually Bucklescript has first class support for working in (and it is actually suggested to use) Facebook’s Reason. ^.^

Heh, it is not a bad design at all. I actually quite like the look of Flow too. Javascript is still mutable in a realm that I do not think it should be though, so it still has that wart. ^.^

@OvermindDL1 Can I ask why you are not considering Typescript? The ecosystem and tooling for it are fantastic and it’s very mature now.

Perhaps because its Syntax is very close to JavaScripts and not everyone does like that. Also you can do stuff in MLs typesystem that is impossible in TypeScripts (AFAIK).