知らない言語を使ってみたい - ReasonML編 (OCaml な AltJS)

どこか遠くへ行きたい

今日は少し節約です。『知らない言語』じゃないので、看板に偽りあります。
既に 1 年前に調べ尽くしているので、今回は前回からの変更点のみをまとめました。
最近、ブラウザ拡張をひとつ ReasonML で書いているので、改めて変更点まとめておきたかったのもあります。

[前回の記事 : Reason 使った感想 on Windows](https://qiita.com/kikuchi_kentaro/items/5957abcda9fa830db2b7) 

# ReasonML

[公式サイト : https://reasonml.github.io/](https://reasonml.github.io/)
[公式ドキュメント : https://reasonml.github.io/docs/en/what-and-why](https://reasonml.github.io/docs/en/what-and-why)
[API ドキュメント : https://reasonml.github.io/api/index](https://reasonml.github.io/api/index)
[リポジトリ : https://github.com/facebook/reason](https://github.com/facebook/reason)

> Reason lets you write simple, fast and quality type safe code while leveraging both the JavaScript & OCaml ecosystems.

|  |        | 
|-----------------:|------------------|
| 用途 | 一応汎用だが、 Web 志向 | 
| 開始 | 2016/02 | 
| 最新バージョン | 3.0.4 ( 2017/12/9 )| 
| ライセンス | MIT |
| プラットフォーム | JavaScript |
| 型 | 静的型付け | 
| パッケージ管理 | npm | 
| GitHubスター | 6902 ( 2018/12/02 ) |

- AltJS
    - 高速なコンパイル
        - [BuckleScript](https://bucklescript.github.io/) が Reason / OCaml コードを JavasScript にコンパイルする
    - リーダブルな JavaScript の生成
- OCaml の Alternate Syntax
    - OCaml の強力な型システム
    - 関数型の恩恵

OCaml をベースに JSer にも親和性の高い Syntax が加えられた新しい言語みたいなイメージ。

# インストール

- Windows 10 Pro 1803

```powershell
$ npm install -g bs-platform
```

これだけで導入は完了。Windows でも問題なし。素晴らしい。

# エディタ支援

[Editor Plugins - Reason](https://reasonml.github.io/docs/en/editor-plugins)

VS Code が推奨されているので、素直にそれを使う。

これまでは、以下のプラグインを利用していたが、
https://marketplace.visualstudio.com/items?itemName=freebroccolo.reasonml

今後は公式に作られている以下プラグインの方が良いだろう。
https://marketplace.visualstudio.com/items?itemName=jaredly.reason-vscode

# サンプル体験

コードの書き心地をするだけなら、[Playground](https://reasonml.github.io/en/try) が用意されているが、実際の開発をイメージするためにローカルで始める。

## プロジェクト作成

```powershell
$ bsb -init sample_1206 -theme basic-reason

$ cd sample_1206
$ tree
# .
# ├── bsconfig.json
# ├── node_modules
# │  └── bs-platform -> /mnt/c/Users/username/scoop/persist/nodejs/bin/node_modules/bs-platform
# ├── package.json
# ├── README.md
# └── src
#    └── Demo.re
```

## コード

ちょっとコードを変更してみる。

```ocaml
let fizzbuzz = (i) =>
  switch (i mod 3, i mod 5) {
    | (0, 0) => "FizzBuzz"
    | (0, _) => "Fizz"
    | (_, 0) => "Buzz"
    | _    => string_of_int(i)
  }

for (i in 1 to 100) {
  print_endline(fizzbuzz(i));
}
```

## コンパイル

で、コンパイルすると、

```powershell
$ npm run build
```

`src/Demo.bs.js` が生成される。

```javascript:src/Demo.bs.js
// Generated by BUCKLESCRIPT VERSION 4.0.7, PLEASE EDIT WITH CARE
'use strict';


function fizzbuzz(i) {
  var match = i % 3;
  var match$1 = i % 5;
  if (match !== 0) {
    if (match$1 !== 0) {
      return String(i);
    } else {
      return "Buzz";
    }
  } else if (match$1 !== 0) {
    return "Fizz";
  } else {
    return "FizzBuzz";
  }
}

for(var i = 1; i <= 100; ++i){
  console.log(fizzbuzz(i));
}

exports.fizzbuzz = fizzbuzz;
/*  Not a pure module */
```

ちなみに、`package.json` は、

```json:package.json
{
  "name": "sample_1206",
  "version": "0.1.0",
  "scripts": {
    "build": "bsb -make-world",
    "start": "bsb -make-world -w",
    "clean": "bsb -clean-world"
  },
  "keywords": [
    "BuckleScript"
  ],
  "author": "",
  "license": "MIT",
  "devDependencies": {
    "bs-platform": "^4.0.7"
  }
}
```

## 実行

`Import` も無いただの JavaScript なので、普通に Node で実行する。

```powershell
$ node .\src\Demo.bs.js
# 8
```

基本的には、これで終わり。

もし Webpack を使うなら、 [loader は使わず BuckleScript の `in-source` モードを使ってビルドとバンドルを分けるのが作法とのこと](https://bucklescript.github.io/docs/en/interop-with-js-build-systems)。Webpack と bs-platform の２つの Watcher を動かすのはちょっと面倒だけど。

Parcel を使うなら、[全部解決してくれる](https://parceljs.org/reasonML.html)。

# 1 年で進歩した点

[前回の記事 : Reason 使った感想 on Windows](https://qiita.com/kikuchi_kentaro/items/5957abcda9fa830db2b7) 

https://github.com/facebook/reason/blob/master/HISTORY.md

## 1. 公式推奨の VS Code 拡張が作られた

[**過去記事 : VS Code で開発する**](https://qiita.com/kikuchi_kentaro/items/5957abcda9fa830db2b7#vs-code-%E3%81%A7%E9%96%8B%E7%99%BA%E3%81%99%E3%82%8B)

VS Code の拡張が新たに作られた。

https://marketplace.visualstudio.com/items?itemName=jaredly.reason-vscode
https://github.com/jaredly/reason-language-server

[旧拡張](https://marketplace.visualstudio.com/items?itemName=freebroccolo.reasonml) では、Windows での構築に相当手間取り、しかも最終的には上手く動かなかった。

新拡張はその点、導入もスムーズだ。特別な理由が無ければ、新拡張で良さそう。
ただ、**モジュール変更 ~ 反映** までのタイムラグが結構あるのが気になる。まぁ、これは旧拡張でもあるもので、結局は WSL で BS を動かす辛さでしか無いのだが。

## 2. Belt という標準ライブラリ的モジュールの提供

[**過去記事 : 6. 標準モジュール**](https://qiita.com/kikuchi_kentaro/items/5957abcda9fa830db2b7#6-%E6%A8%99%E6%BA%96%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB)

Reason には、元々 OCaml の標準ライブラリ ( Pervasives ) をそのまま持ってきたみたいなモジュールがデフォルトで `open` されていたが、もう少し **Reason っぽい API の標準ライブラリ** は無いものかと思っていた。

公式に Belt という標準ライブラリが提供されるようになった。
https://bucklescript.github.io/bucklescript/api/Belt.html

これは元々、[便利機能を実装する予定だった BS モジュール](https://qiita.com/kikuchi_kentaro/items/5957abcda9fa830db2b7#62-bucklescript-libraries) が、名前を変えて実現したもののようだ。

### 例外の可能性のあるメソッド名に `Exn` サフィックスが付く

```ocaml:before
let head = try(List.hd([])) {
| Failure("hd") => 10
};
```

```ocaml:after
open Belt;
let head = try(List.headExn([])) {
| Js.Exn.Error(_) => 10
};
```

例外の型まで変わっているのは気になる。JavaScript 側に相当寄せてきているんだろうか。

### コレクション操作関数の引数の順番が変わった

```ocaml:before
let odds = List.map((x) => x * 2, [1, 3, 5, 10]);
```

```ocaml:after
open Belt;
let odds = List.map([1, 3, 5, 10], (x) => x * 2);
```

こうなると、気になるのが `|>` の使い方で、あれはただ値を関数に渡しているだけだったが、位置が変わったら使えなくなる。

```ocaml:before
[1, 3, 5, 10]
  |> List.map((x) => x * 2)
  |> List.filter((x) => x > 7)
  |> List.iter((x) => Js.log(x))
```

この場合 `|>` は、 `List.map` に与えられた関数が適用されて返された  `List => List` の関数に、`[1,3,5,10]` を適用しているだけだ。順番が変わると使えない。

これに対しては、新たに `->` ( Fast Pipe と呼ばれる ) が追加された。
こんな感じで使える。

```ocaml:after
open Belt;
[1, 3, 5, 10]
  -> List.map((x) => x * 2)
  -> List.keep((x) => x > 7)
  -> List.forEach((x) => Js.log(x))
```

この場合、 パイプ元を第一引数に適用する。

また、`|>` と一緒に `_` プレースポルダーを置くことで任意の位置に値を適応することも可能。

```ocaml:another
open Belt;
[1, 3, 5, 10]
  |> List.map(_, (x) => x * 2)
  |> List.keep(_, (x) => x > 7)
  |> List.forEach(_, (x) => Js.log(x))
```

この場合、関数のネストではなく、関数の演算結果が逐一変数に保存されるよな処理に置き換えられる。

### よく使うデータ型の Generic Module の提供

```ocaml:before
module StringMap = Map.Make(String)
let m = StringMap.(
  empty
  |> add("key 1", 1)
  |> add("key 2", 2)
)
```

```ocaml:after
open Belt;
let m = Map.String.fromArray([|("key 1", 1), ("key 2", 2)|])
```

`Int` と `String` のコレクションは提供されているようだ。

### uncurried な Callback を受け取るメソッドには `U` サフィックスが付く

```ocaml:after
open Belt;

/* コンパイルエラー */
[1, 3, 5, 10]
  -> List.forEachU((x) => Js.log(x))

/* 正しくはこっち */
[1, 3, 5, 10]
  -> List.forEachU((. x) => Js.log(x))
```

uncurried のほうがパフォーマンスが良いらしい。
uncurry についての詳細は [こちら](https://bucklescript.github.io/docs/en/function.html#solution-guaranteed-uncurrying) を参照。


## 3. boolean の扱いが改善した

[**過去記事 : 8.3. 実行時セマンティクス**](https://qiita.com/kikuchi_kentaro/items/5957abcda9fa830db2b7#3-%E5%AE%9F%E8%A1%8C%E6%99%82%E3%82%BB%E3%83%9E%E3%83%B3%E3%83%86%E3%82%A3%E3%82%AF%E3%82%B9)

これまでは、Reasonml の `true` / `false` が JavaScript の `0` / `1` に変換されていたため、思いもよらない動作をすることがあった。
その為、JavaScript と bool のやり取りをする時は `Js.boolean` を使わなければいけなかった。

それがいつの間にか、JavaScript の bool に変換されるようになっていた。
小さいことだが、引っかかると結構なストレスだったので良かった。

## 4. セミコロンが強制ではなくなった

文末のセミコロンが強制ではなくなった。
未だにフォーマッタを利用すると文末にセミコロンは追加される。

# 思ったより進んでいなかった点

## 1. async/await の決着がついていない

ずーっと議論されている、async/await をどのような形で Reason に取り込むかという問題が、なかなかまとまらない。

https://github.com/facebook/reason/issues/1321
https://jaredforsyth.com/posts/building-async-await-in-reason/

## 2. 1年経っても Qiita のシンタックスハイライトが対応しない

まだまだ有名じゃないってことなんだろうか。
ちなみに、[pony という言語の 『pony』タグは Qiita に 6 記事しか無いがシンタックスハイライトに対応している](https://qiita.com/tags/pony) 。

それはさておき、どちらの言語にももっと広まってほしい。


# 参考

https://medium.com/reasonconf/reasonml-conference-2018-8283b1e4c437

https://github.com/vramana/awesome-reasonml

https://jaredforsyth.com/



# あとがき
※ この記事は個人の見解であり、所属する組織を代表するものではありません。