この記事は ACCESS Advent Calendar 2018、2日目の記事です。
どうも、仕事ではECMAScript3をよく書いている @soebosi です。
趣味でReasonMLを書いているので、今回はその話をします。
ReasonML
ReasonMLは、Facebook社が開発しているプログラミング言語です。
JavaScriptにトランスパイルできてReactを使うことを前提としているので、JSX的な構文も言語の機能として持っています。
JavaScriptとのbinding機構も用意されていて、構造に合わせていくつかの手段があります。
今回は、binding機構についてまとめたいと思います。
くわしくは、 https://bucklescript.github.io/en/ の公式ドキュメントを参照ください。
(ReasonMLといっておきながら、JavaScriptのbinding機構はBuckleScriptによってもたらされているので、BuckleScriptのドキュメントを見る必要あり)
動作確認したいときは、公式のplaygroundがおすすめです。
https://reasonml.github.io/en/try
%raw, %%raw
JavaScriptのプログラムを直接文字列で記述する方法です。
%raw
は、そのままReasonML側の式に組み込むときに使用して、%%raw
はトップレベルでの宣言で使用します。
let add = [%raw "(a, b) => a + b"];
[%%raw "console.log(\"hello world\")"];
Js.log(add(1, 2)); /* 3 */
これをJavaScriptにトランスパイルすると、こんな感じ。
var add = ((a, b) => a + b);
console.log("hello world")
;
console.log(Curry._2(add, 1, 2));
ReasonMLは、ダブルクォートで囲む方法以外に{|
と|}
で囲むことでも文字列を作成できます。
%raw
, %%raw
でも使用できるので、ダブルクォートをJavaScript側で使いたいときには、エスケープしなくてもダブルクォートを埋め込むことができます。
さきほどの%%raw
の例を書き換えると、以下みたいにできます。
[%%raw {|console.log("hello world")|}];
型はどうなってるの?
%%raw
については、ReasonMLの世界に進出してきていないのでよいのですが、%raw
ではReasonMLの値として使用できてしまいます。すなわち型があるはずです。
https://reasonml.github.io/docs/en/interop#dumping-in-some-javascript-and-making-it-accessible-from-reason
を見てみると、「magic type」なんて表現がされていますね。
ちょっと実験した感じ、どの型とも比較ができたので、anyのような型なのだと思います。
型を指定する方法も用意されているみたいで、以下の書き方ができます。
let add: (int, int) => int = [%raw (a, b) => "return a + b"];
@bs.val
グローバルに宣言されいてる値をbindingする時に使うのが、@bs.val
です。
external
の後にReasonML側で使用する名前と型定義、=
の後にJavaScript側の名前を記述します。ReasonML側で使用している名前がJavaScriptと同じ場合は、空文字として省略ができます。
[@bs.val] external setTimeout : (unit => unit, int) => float = "setTimeout";
/* or */
[@bs.val] external setTimeout : (unit => unit, int) => float = "";
setTimeout(() => Js.log("Hello World"), 100);
JavaScriptっぽくそのまま使えて素敵ですね。
出力されるJavaScriptも素直で読みやすいです。
setTimeout((function () {
console.log("Hello World");
return /* () */0;
}), 100);
@bs.scope
setTimeoutのようなグローバルに定義された関数であれば、@bs.val
だけでbindingできますが、グローバルなオブジェクトに紐づく関数をbindingしたい場合、@bs.scope
を使う必要があります。
[@bs.val] [@bs.scope "Math"] external floor : float => int = "";
Js.log(floor(5.2)); /* 5 */
console.log(Math.floor(5.2));
nestされている場合は、カンマ区切りを括弧で囲むといけるようです。
[@bs.val] [@bs.scope ("window", "location")] external href: string = "";
@bs.new
newを使って生成されたインスタンスをbindingしたい場合は、@bs.new
を使ってJavaScript側のコンストラクタをbindingします。
type date;
[@bs.new] external createDate : string => date = "Date";
Js.log(createDate("1995-12-17T03:24:00"));
console.log(new Date("1995-12-17T03:24:00"));
@bs.send
生成されたオブジェクトのメソッドをbindingするときには、@bs.send
を使います。
ReasonML側の関数としては、第一引数に@bs.new
で生成した型を渡します。
/* "new.re のつづき" */
[@bs.send] external getTime : date => int = "";
let d = createDate("1995-12-17T03:24:00");
Js.log(d->getTime); /* "「->」は実はパイプ演算子で、「Js.log(getTime(d));」と同じ意味になります。" */
var d = new Date("1995-12-17T03:24:00");
console.log(d.getTime());
読める形でトランスパイルされるのは、本当に素晴らしいですね。
@bs.module
今までのbindingは組み込みのものでしたが、外部のモジュールをimport/exportしてbindingしたい場合もあります。
そのときは、@bs.module
が利用できます。
読み込みたいモジュール名を@bs.module
の後ろに書きます。
[@bs.module "path"] external basename : (string, string) => string = "";
Js.log(basename("/hoge/fuga/piyo.txt", "txt")); /* piyo */
var Path = require("path");
console.log(Path.basename("/hoge/fuga/piyo.txt", "txt"));
@bs.variadic(bs.splite)
可変長引数のJavaScript関数を扱う場合は、@bs.variadic
が使用できます。
(最近、@bs.splite
からリネームされたみたいで、playgroundでは、@bs.splite
しか使えませんでした)
@bs.variadic
は、ReasonMLでの配列をJavaScriptの可変長引数として展開します。
使う場合は、ReasonML側の末尾の引数がarray('a)である必要があります。
[@bs.module "path"] [@bs.variadic]
external join : array(string) => string = "";
Js.log(join([|"hoge", "fuga", "piyo"|])); /* piyo */
var Path = require("path");
console.log(Path.join("hoge", "fuga", "piyo"));
まとめ
ReasonMLのすばらしいところの一つに型による安全性があると思います。
究極%bs.raw
を使えば、何でもbindingできるのですが、型の恩恵が受けられなくなってしまいます。
適切なbinding機構を選択しつつ、きちんとJavaScriptとのbindingに型を定義して、堅牢なアプリケーションを作りたいものです。
実は他にも@bs.set
, @bs.meth
, @bs.obj
みたいなbinding機構があるようですが、また別の機会にまとめることにします。
明日は、 @aKatsuhiroMihara のGo言語の話です!お楽しみに!