Slide 1

Slide 1 text

Exploring Reason's Interop with JavaScript (and by "Reason" I mean "Bucklescript") ReasonML Munich Meetup, February 1st, 2018 1

Slide 2

Slide 2 text

David Gomes Software Engineer at MemSQL (we are building a very fast database) 2

Slide 3

Slide 3 text

Why care about Reason's interop with JavaScript? 3

Slide 4

Slide 4 text

How do I use JavaScript from Reason? [%%raw "var a = 5"]; let add = [%raw {| function(b) { console.log("hello from raw JavaScript!"); return a + b } |}]; Js.log(add(2)); (* 7 *) 4

Slide 5

Slide 5 text

[%%raw "var a = 5"]; let add = [%raw {| function(b) { console.log("hello from raw JavaScript!"); return a + b } |}]; Js.log(add(2)); (* 7 *) Js.log(add + 8); (* ????????????? *) 5

Slide 6

Slide 6 text

let add: ((int, int) => int) = [%raw {| function(a, b) { console.log("hello from raw JavaScript!"); return a + b } |}]; Js.log(add(5, 6)); 6

Slide 7

Slide 7 text

How can we make this even better? More declarative, more reproducible, easier to reason about 7

Slide 8

Slide 8 text

[@bs.val] external sqrt : float => float = "Math.sqrt"; let four = sqrt(16.0); (* => 4*) 8

Slide 9

Slide 9 text

What operators are there? We are going to skip most of them Source: https://bucklescript.github.io/docs/en/interop-cheatsheet.html 9

Slide 10

Slide 10 text

Operator Use [@bs.new] new Instance() [@bs.val] variables/functions [@bs.send] obj.method() [@bs.obj] creating JavaScript objects [@bs.splice] functions with unknown number of homogenous args … … 10

Slide 11

Slide 11 text

Let's go through a real life use case 11

Slide 12

Slide 12 text

Luxon — Example #1 12

Slide 13

Slide 13 text

const { DateTime } = require("luxon"); DateTime .local() .setZone("America/New_York") .minus({ weeks: 1 }) .endOf("day"); 13

Slide 14

Slide 14 text

type dt; external local : () => dt => "local"; 14

Slide 15

Slide 15 text

type dt; [@bs.module "luxon"] [@bs.scope "DateTime"] [@bs.val] external local : unit => dt = ""; let myDate = local(); (* DateTime { ts: 2018-01-11T22:51:14.520+00:00, zone: Europe/Lisbon, locale: und } *) 15

Slide 16

Slide 16 text

// Generated by BUCKLESCRIPT VERSION 2.1.0, PLEASE EDIT WITH CARE 'use strict'; var Luxon = require("luxon"); var myDate = Luxon.DateTime.local(); console.log(myDate); exports.myDate = myDate; /* myDate Not a pure module */ 16

Slide 17

Slide 17 text

[@bs.send.pipe : dt] external setZone : string => dt = ""; 17

Slide 18

Slide 18 text

(* The type of setZone is: (string, dt) => dt *) let myDate = local() |> setZone("America/New_York"); 18

Slide 19

Slide 19 text

19

Slide 20

Slide 20 text

[@bs.send.pipe : dt] external minusMs : int => dt = "minus"; [@bs.send.pipe : dt] external minus : duration => dt = "minus"; [@bs.send.pipe : dt] external minusObj : durationObj => dt = "minus"; 20

Slide 21

Slide 21 text

We could also use polymorphic variant types… [@bs.send.pipe : dt] external minus : ([@bs.unwrap] [ | `Duration(duration) | `DurationObj(durationObj) | `Int(int) ]) => dt = ""; (see github.com/davidgomes/bs-luxon to see how) 21

Slide 22

Slide 22 text

type durationObj = { . "weeks": int, "months": int, }; [@bs.obj] external makeDurObj : ( ~weeks: int=?, ~months: int=?, unit ) => durationObj = ""; [@bs.send.pipe : dt] external minusObj : durationObj => dt = "minus"; 22

Slide 23

Slide 23 text

let myDate = local() |> setZone("America/New_York") |> minusObj(makeDurObj(~weeks=1, ())); Note that here, one should hide the calling of makeDurObj by shadowing it using this technique. 23

Slide 24

Slide 24 text

[@bs.send.pipe : dt] external endOf : ( [@bs.string] [ | `year | `month | `week | `day | `hour | …] ) => dt = ""; 24

Slide 25

Slide 25 text

DateTime .local() .setZone("America/New_York") .minus({ weeks: 1 }) .endOf("day"); let myDate = local() |> setZone("America/New_York") |> minusObj(makeDurObj(~weeks=1, ())) |> endOf(`day); 25

Slide 26

Slide 26 text

Example #2 https://bucklescript.github.io/bucklescript/api/Js.html The Js Module 26

Slide 27

Slide 27 text

var mysql = require("mysql"); var connection = mysql.createConnection({ host: "127.0.0.1", user: "root" }); connection.query("SHOW DATABASES", function (error, results, fields) { if (error) { console.log(error); return; } console.log(results, fields); }); connection.end(); (source: github.com/mysqljs/mysql) 27

Slide 28

Slide 28 text

let conn = Mysql.createConnection( ~host="127.0.0.1", ~port=3306, ~user="root", () ); Mysql.query(conn, "SHOW DATABASES", (error, results, fields) => switch (Js.Nullable.to_opt(error)) { | None => Js.log(results); Js.log(fields); | Some(error) => Js.log(error##message) } ); Mysql.endConnection(conn); 28

Slide 29

Slide 29 text

let conn = Mysql.createConnection( ~host="127.0.0.1", ~port=3306, ~user="root", () ); Mysql.query(conn, "SHOW DATABASES", result => { switch (result) { | Ok(results) => Js.log(results.results) | Error(err) => Js.log(err##message) } }); Mysql.endConnection(conn); 29

Slide 30

Slide 30 text

The other way round 30

Slide 31

Slide 31 text

A Happy Example 31

Slide 32

Slide 32 text

let isAre = n => n == 1 ? "is" : "are"; 32

Slide 33

Slide 33 text

// Generated by BUCKLESCRIPT VERSION 2.1.0, PLEASE EDIT WITH CARE 'use strict'; function isAre(n) { var match = +(n === 1); if (match !== 0) { return "is"; } else { return "are"; } } exports.isAre = isAre; /* No side effect */ 33

Slide 34

Slide 34 text

const { isAre } = require("./demo.bs"); const word = "meetups"; console.log(`${word} ${isAre(word, 3)} fun`); // => meetups are fun 34

Slide 35

Slide 35 text

A different type of example 35

Slide 36

Slide 36 text

let requiredFieldValidator = value => if (String.length(value) == 0) { Js.Option.some("Field is required."); } else { None; }; switch (requiredFieldValidator("test")) { | None => Js.log("all good") | Some(err) => Js.log("error: " ++ err) } 36

Slide 37

Slide 37 text

const { requiredFieldValidator } = require("./demo.bs"); console.log(requiredFieldValidator("test")); // => 0 37

Slide 38

Slide 38 text

Internal Representations for Reason Types Operator Use int number string string tuple array list [] -> 0 [1] -> 1 [1, 2] -> [1, [2, 0]] option('a) None -> 0 Some(a) -> [a] - … 38

Slide 39

Slide 39 text

Warning: Some of these representations might change — be careful. 39

Slide 40

Slide 40 text

Reason let requiredFieldValidatorJs = value => Js.Null.from_opt(requiredFieldValidator(value)); JavaScript const { requiredFieldValidatorJs } = require("./demo.bs"); console.log(requiredFieldValidatorJs("test")); // => null 40

Slide 41

Slide 41 text

A third type of example 41

Slide 42

Slide 42 text

type person = { age: int, name: string }; let personToString = (p: person) => { let name = p.name; let age = p.age; {j|$(name) is $(age) years old|j}; }; let david = { age: 22, name: "David Gomes" }; Js.log(personToString(david)); (* => "David Gomes is 22 years old" *) 42

Slide 43

Slide 43 text

DO NOT DO THIS const { personToString } = require("./demo.bs"); console.log(personToString([22, "david"])); 43

Slide 44

Slide 44 text

[@bs.deriving jsConverter] type person = { age: int, name: string }; const { personFromJs, personToString } = require("./demo.bs"); console.log(personToString(personFromJs({ name: "David", age: 22, }))); 44

Slide 45

Slide 45 text

Key Takeaways 45

Slide 46

Slide 46 text

1 Think twice before you start to bind 46

Slide 47

Slide 47 text

2 Gradually reach full type safety, don't be afraid to start slowly 47

Slide 48

Slide 48 text

3 Bindings don't need to be 1-1 mappings of the original API (and probably shouldn't be) 48

Slide 49

Slide 49 text

4 You can (and should) write test for bindings test("DateTime.local(2017) is the beginning of the year", () => { let dt = DateTime.local(~year=2017, ()); expect(getDateObject(dt)) |> toEqual({ "year": 2017, "month": 1, "day": 1, "hour": 0, "minute": 0, "second": 0, "millisecond": 0 }); }); 49

Slide 50

Slide 50 text

5 You should use interface (.rei/.mli) files 50

Slide 51

Slide 51 text

6 Inspect your JS output every now and then 51

Slide 52

Slide 52 text

7 Writing bindings is a great way of learning Reason/OCaml 52

Slide 53

Slide 53 text

8 Having JavaScript clients consume your Reason libraries might require some work. 53

Slide 54

Slide 54 text

Thank you! @davidrfgomes on Twitter github.com/davidgomes 54