Docs / BuckleScript / NewBucklescriptSyntax

New BuckleScript Syntax

BuckleScript 8.1 comes with a brand new syntax parser and printer that allows for much better syntax error messages and other benefits we will talk about soon.

More details about the new syntax can be found in this early announcement blog post.

This document gives an overview over the most relevant features until more details / docs are revealed.

Note: In case you are looking for the current Reason language documentation (v3.6), you can find it here.

What is Different to Reason v3.6?

  • Complete removal of semicolon (you can still write them).

  • No need for parentheses around if, switch and try.

  • Type arguments: from option(int) to option<int>.

  • Interpolated string: from {j|hello ${name}|j} to j`hello ${name}` (8.1.1 update: now it also works without j, as the type-safe version). The string now has proper unicode support!

  • Polymorphic variants: from `red to #red.

  • Arrays: from [|1,2,3|] to [1,2,3]. In JS, arrays are the right default.

  • Lists: from [1,2,3] to list[1,2,3] (8.1.1 update: now it is list{1, 2, 3}). This ties with upcoming plans to access containers in a uniform way: set[...] and map[...]. Maybe temporary.

  • Exception: from try (compute()) { | Not_found => Js.log("oops")} to try compute() catch { | Not_found => Js.log("oops")}.

  • First class module: from (module S: Student) to module(S: Student).

  • No custom infix operator for now (including mod).

  • Object access: from settings##visible #= true to settings["visible"] = true. Rejoice!

  • Object: from Js.t({"age": int}) to just {"age": int}. The Js.t part is now implicit.

  • Attribute: from [@bs.deriving accessors] to @bs.deriving(accessors). From [%re bla] to %re(bla).

  • Removed dereference syntax result^. Just use result.contents.

  • `hello world` for multiline strings. "hello world" string is now singleline.

  • fun pattern matching syntax removed.

  • Type declaration is non-recursive by default, consistent with let bindings. To have recursive types, use type rec myList<'a> = Nil | Cons('a, myList<'a>).

  • Use any words, including reserved keywords, as your identifier name: let \"try" = true.

How to Use the New Syntax

Install bs-platform@8.1 and create a file with the .res or .resi file extension to enable the new syntax parser (no further configuration required).

Quick Example

if hasEaten { Js.log("more dessert please") } else { Js.log("dessert please") } let message = j`Hello ${userName->Js.String.toUpperCase}!` type student<'extraInfo> = { name: string, age: int, otherInfo: 'extraInfo, } @bs.val external window: {..} = "window" window["addEventListener"]("focus", onFocus)

Comparison to JS

Let's have a look on how different JS constructs look in our new syntax:


/* Comment */Same
// Line commentSame


const x = 5;let x = 5
var x = y;No equivalent (thankfully)
let x = 5; x = x + 1;let x = ref(5); x := x.contents + 1

String & Character

"Hello world!"Same
'Hello world!'Strings must use "
"hello " + "world""hello " ++ "world"
`hello world`Same


true, falseSame
||, &&, <=, >=, <, >Same
a === b, a !== bSame
No deep equality (recursive compare)a == b, a != b
a == bNo equality with implicit casting (thankfully)


3Same *
3 + 4Same
3.0 + 4.53.0 +. 4.5
5 % 3mod(5, 3)

* JS has no distinction between integer and float.


no typestype point = {x: int, mutable y: int}
{x: 30, y: 20}Same
point.y = 30;Same
{...point, x: 30}Same


[1, 2, 3]Same
myArray[1] = 10Same
[1, "Bob", true](1, "Bob", true) *

* Heterogenous arrays in JS are disallowed for us. Use tuple instead.


null, undefinedNone *

* Again, only a spiritual equivalent; we don't have nulls, nor null bugs! But we do have an option type for when you actually need nullability.


arg => retValSame
function named(arg) {...}let named = (arg) => {...}
const f = function(arg) {...}let f = (arg) => {...}
add(4, add(5, 6))Same


const myFun = (x, y) => { const doubleX = x + x; const doubleY = y + y; return doubleX + doubleY };
let myFun = (x, y) => { let doubleX = x + x let doubleY = y + y doubleX + doubleY }


if (a) {b} else {c}if a {b} else {c}
a ? b : cSame
switchswitch but super-powered pattern matching!

* Our conditionals are always expressions! You can write let result = if a {"hello"} else {"bye"}


const {a, b} = datalet {a, b} = data
const [a, b] = datalet [a, b] = data *
const {a: aa, b: bb} = datalet {a: aa, b: bb} = data

* Gives good compiler warning that data might not be of length 2.


for (let i = 0; i <= 10; i++) {...}for i in 0 to 10 {...}
for (let i = 10; i >= 0; i--) {...}for i in 10 downto 0 {...}
while (true) {...}while true {...}


<Foo bar=1 baz="hi" onClick={bla} />Same
<Foo bar=bar /><Foo bar /> *
<input checked /><input checked=true />
No children spread<Foo>...children</Foo>

* Argument punning!


throw new SomeError(...)raise(SomeError(...))
try {a} catch (Err) {...} finally {...}try a catch { | Err => ...} *

* No finally.


The last expression of a block delimited by {} implicitly returns (including function body). In JavaScript, this can only be simulated via an immediately-invoked function expression (since function bodies have their own local scope).

let result = (function() { const x = 23; const y = 34; return x + y; })();
let result = { let x = 23 let y = 34 x + y }