ReasonMLでJavaScriptをbindingするあれこれ

この記事は [ACCESS Advent Calendar 2018](https://qiita.com/advent-calendar/2018/access)、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`はトップレベルでの宣言で使用します。

```ocaml:raw1.re
let add = [%raw "(a, b) => a + b"];

[%%raw "console.log(\"hello world\")"];

Js.log(add(1, 2)); /* 3 */
```
これをJavaScriptにトランスパイルすると、こんな感じ。

```js:raw1.bs.js
var add = ((a, b) => a + b);

console.log("hello world")
;

console.log(Curry._2(add, 1, 2));
```

ReasonMLは、ダブルクォートで囲む方法以外に`{|`と`|}`で囲むことでも文字列を作成できます。
`%raw`, `%%raw`でも使用できるので、ダブルクォートをJavaScript側で使いたいときには、エスケープしなくてもダブルクォートを埋め込むことができます。
さきほどの`%%raw`の例を書き換えると、以下みたいにできます。

```ocaml:raw2.re
[%%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のような型なのだと思います。

型を指定する方法も用意されているみたいで、以下の書き方ができます。

```ocaml:raw3.re
let add: (int, int) => int = [%raw (a, b) => "return a + b"];
```

## @bs.val

グローバルに宣言されいてる値をbindingする時に使うのが、`@bs.val`です。
`external`の後にReasonML側で使用する名前と型定義、`=`の後にJavaScript側の名前を記述します。ReasonML側で使用している名前がJavaScriptと同じ場合は、空文字として省略ができます。

```ocaml:val.re
[@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も素直で読みやすいです。

```js:val.bs.js
setTimeout((function () {
        console.log("Hello World");
        return /* () */0;
      }), 100);
```

### @bs.scope

setTimeoutのようなグローバルに定義された関数であれば、`@bs.val`だけでbindingできますが、グローバルなオブジェクトに紐づく関数をbindingしたい場合、`@bs.scope`を使う必要があります。

```ocaml:scope1.re
[@bs.val] [@bs.scope "Math"] external floor : float => int = "";

Js.log(floor(5.2)); /* 5 */
```

```js:scope1.bs.js
console.log(Math.floor(5.2));
```

nestされている場合は、カンマ区切りを括弧で囲むといけるようです。

```ocaml:scope2.re
[@bs.val] [@bs.scope ("window", "location")] external href: string = "";
```

## @bs.new

newを使って生成されたインスタンスをbindingしたい場合は、`@bs.new`を使ってJavaScript側のコンストラクタをbindingします。

```ocaml:new.re
type date;
[@bs.new] external createDate : string => date = "Date";

Js.log(createDate("1995-12-17T03:24:00"));
```

```js:new.bs.js
console.log(new Date("1995-12-17T03:24:00"));
```

### @bs.send

生成されたオブジェクトのメソッドをbindingするときには、`@bs.send`を使います。
ReasonML側の関数としては、第一引数に`@bs.new`で生成した型を渡します。

```ocaml:send.re
/* "new.re のつづき" */

[@bs.send] external getTime : date => int = "";

let d = createDate("1995-12-17T03:24:00");
Js.log(d->getTime); /* "「->」は実はパイプ演算子で、「Js.log(getTime(d));」と同じ意味になります。" */
```

```js:send.bs.js
var d = new Date("1995-12-17T03:24:00");

console.log(d.getTime());
```

読める形でトランスパイルされるのは、本当に素晴らしいですね。

## @bs.module

今までのbindingは組み込みのものでしたが、外部のモジュールをimport/exportしてbindingしたい場合もあります。
そのときは、`@bs.module`が利用できます。
読み込みたいモジュール名を`@bs.module`の後ろに書きます。

```ocaml:module.re
[@bs.module "path"] external basename : (string, string) => string = "";

Js.log(basename("/hoge/fuga/piyo.txt", "txt")); /* piyo */
```

```js:module.bs.js
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)である必要があります。

```ocaml:variadic.re
[@bs.module "path"] [@bs.variadic]
external join : array(string) => string = "";

Js.log(join([|"hoge", "fuga", "piyo"|])); /* piyo */
```

```js:variadic.bs.js
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言語の話です！お楽しみに！