Dans un précédent article, on vous présentait une nouvelle syntaxe pour OCaml: ReasonML, elle rend le langage plus accessible en le rapprochant de JavaScript moderne.
À l'aide du projet BuckleScript qui supporte Reason nativement, on peut compiler notre code Reason vers JavaScript très facilement. Le créateur de BuckleScript utilisait à l'origine un autre projet appelé js_of_ocaml. Trouvant qu'il serait possible d'optimiser et de rendre plus lisible le code JavaScript en sortie si le compiler commençait son travail à une étape plus haut niveau (une représentation du programme contenant des informations supplémentaires, alors que js_of_ocaml utilise du bytecode), il propose ce changement à la team js_of_ocaml qui refuse, et décide donc de se lancer dans le projet qui deviendra BuckleScript.
Pour démarrer un projet avec BuckleScript et Reason, on ouvre son terminal, et c'est parti:
On installe BuckleScript:
$ npm install -g bs-platform
On initialise le projet:
$ bsb -init my-app -theme basic-reason
Hop, votre projet est prêt dans my-app
.
Maintenant la question est: comment est-ce que je peux utiliser du JS dans Reason et vice-versa ?
OCaml gère naturellement les FFI
, notamment pour appeler des fonctions C
lorsqu'il compile vers du code natif.
BuckleScript vient les overloader pour les adapter à JavaScript.
Créons une FFI pour la fonction alert
:
[@bs.val] external alert : string => unit = "";
On définit:
alert
string
et ne retourne rien (ici représenté par la valeur
unit
)[@bs.val]
)Si on regarde le code JavaScript en sortie, c'est vide. En effet, external
est
un moyen de définir comment accéder à une valeur ainsi que son type. Si en
revanche on utilise la function alert
dans le module:
[@bs.val] external alert : string => unit = "";
alert("Hello!");
On voit dans l'output que BuckleScript a inliné l'appel de alert
, il n'a pas
crée de représentation intermédiaire.
// Generated by BUCKLESCRIPT VERSION 1.9.1, PLEASE EDIT WITH CARE
"use strict";
alert("Hello!");
/* Not a pure module */
Maintenant amusons nous à créer des bindings pour jQuery, juste pour le fun:
/* On crée un type opaque pour représenter un objet jQuery */
type jQuery;
/* On type le module jQuery */
[@bs.module] external jQuery : string => jQuery = "jquery";
/* On type la méthod `on`, BuckleScript peut naturellement typer
le pattern de chaining, assez commun en JS, à l'aide de l'annotation
`bs.send.pipe: type` */
[@bs.send.pipe: jQuery] external on : string => (Dom.event => unit) => jQuery = "";
jQuery(".selector")
|> on("click", (_) => alert("hey"));
Ce qui va nous sortir:
// Generated by BUCKLESCRIPT VERSION 1.9.1, PLEASE EDIT WITH CARE
"use strict";
var JQuery = require("jquery");
JQuery(".selector").on("click", function() {
alert("hey");
return /* () */ 0;
});
/* Not a pure module */
Comme on peut le constater, le code de sortie ressemble beaucoup à ce qu'on pourrait écrire à la main.
Pour en savoir un peu plus sur les FFI JavaScript:
bs-{nom-de-lib-js}
)On peut directement utiliser des objets JavaScript en Reason. Pour accéder à une
propriété, on utilise ##
.
myJsObject##property
Ça dépanne, mais au sein de notre code Reason, on préférera bien souvent utiliser des records: ils ont une représentation plus légère et sont par défaut immutables. Pour effectuer une conversion, on procède de la manière suivante:
type jsUser = {
.
"id": string,
"username": string,
/* valeur pouvant être null, undefined, ou la valeur */
"birthdate": Js.Null_undefined.t(string),
/* "light" ou "dark", les enums sont souvent représentés par des strings en JS */
"theme": string
};
/* En Reason, les enums sont représentés par des variants */
type theme =
| Light
| Dark;
type user = {
id: string,
username: string,
/* pas de null ou undefined, on utilise un type option */
birthdate: option(string),
theme
};
/* une fonction de transformation JS -> Reason */
let fromJs = (jsUser) => {
id: jsUser##id,
username: jsUser##username,
/* BuckleScript propose des helpers pour les conversions */
birthdate: Js.Null_undefined.to_opt(jsUser##birthdate),
theme:
switch jsUser##theme {
| "dark" => Dark
| "light"
/* On match une chaîne de caractère, le match n'est pas exhaustif,
on doit donc définir la valeur de fallback (par defaut) à l'aide
de `_`
*/
| _ => Light
}
};
/* Pour créer un objet JS en Reason, il suffit de l'écrire comme un
record, mais avec des clés entre quotes, comme du JSON.
*/
let toJs = (user) => {
"id": user.id,
"username": user.username,
"birthdate": Js.Null_undefined.from_opt(user.birthdate),
"theme":
switch user.theme {
| Light => "light"
| Dark => "dark"
}
};
Le code en sortie:
// Generated by BUCKLESCRIPT VERSION 1.9.1, PLEASE EDIT WITH CARE
"use strict";
var Js_primitive = require("bs-platform/lib/js/js_primitive.js");
var Js_null_undefined = require("bs-platform/lib/js/js_null_undefined.js");
function fromJs(jsUser) {
var match = jsUser.theme;
var tmp;
switch (match) {
case "dark":
tmp = /* Dark */ 1;
break;
case "light":
tmp = /* Light */ 0;
break;
default:
tmp = /* Light */ 0;
}
return /* record */ [
/* id */ jsUser.id,
/* username */ jsUser.username,
/* birthdate */ Js_primitive.null_undefined_to_opt(jsUser.birthdate),
/* theme */ tmp,
];
}
function toJs(user) {
var match = user[/* theme */ 3];
return {
id: user[/* id */ 0],
username: user[/* username */ 1],
birthdate: Js_null_undefined.from_opt(user[/* birthdate */ 2]),
theme: match !== 0 ? "dark" : "light",
};
}
exports.fromJs = fromJs;
exports.toJs = toJs;
/* No side effect */
Si le besoin s'en fait sentir, BuckleScript propose naturellement la stdlib de JavaScript.
let myArray = [|1, 2, 3, 4, 5|];
myArray
|> Js.Array.map((item) => item * 2)
|> Js.Array.reduce((acc, item) => acc + item, 0);
vous sortira:
// Generated by BUCKLESCRIPT VERSION 1.9.1, PLEASE EDIT WITH CARE
"use strict";
var myArray = /* array */ [1, 2, 3, 4, 5];
myArray
.map(function(item) {
return item << 1;
})
.reduce(function(acc, item) {
return (acc + item) | 0;
}, 0);
exports.myArray = myArray;
/* Not a pure module */
Pour les cas extrêmes ou vous voulez juste balancer une fonction JS, vous pouvez:
/* %bs.raw pour une expression, %%bs.raw pour un bloc de code arbitraire */
let log: string => unit = [%bs.raw {|
function (a) {
console.log(a);
}
|}];
log("ok");
qui vous sort un joli:
// Generated by BUCKLESCRIPT VERSION 1.9.1, PLEASE EDIT WITH CARE
"use strict";
var Curry = require("bs-platform/lib/js/curry.js");
var log = function(a) {
console.log(a);
};
Curry._1(log, "ok");
exports.log = log;
/* log Not a pure module */
OCaml peut en grande majorité être compilé en JavaScript très simple, parce qu'il partage beaucoup de concepts avec ce dernier. Il est assez simple de créer des ponts entre les deux langages à l'aide des FFI. L'énorme avantage de cette feature, c'est que l'on peut commencer à utiliser Reason incrémentalement sur son projet sans avoir à tout réécrire d'un coup.
Bisous bisous.