ReasonML雑感

# ReasonML

- facebook製プログラミング言語
- JavaScript、バイナリ両方にコンパイル可能
- OCamlとJavaScriptのエコシステムが利用できる
- 強力な型付けとイミュータブルが特徴
- 2018/02/21 時点で最新バージョンは`3.0.4`

---

# BuckleScript

- OCamlのJavaScriptバックエンド
- Bloomberg社製
- ReasonMLはBuckleScriptに乗っかっている
- 2018/02/21 時点で最新バージョンは`2.2.0`


---

# アーキテクチャ

![reason architecture](https://user-images.githubusercontent.com/1909539/31158768-0c7e9d04-a879-11e7-9cfb-19780a599231.png)
https://github.com/facebook/reason/tree/master/src より引用

---

# 構文

- パターンマッチ
- ファンクター(Module Functions)
- パイプ演算子

```ocaml
module IntPairs = {
  type t = (int, int);
  let compare = ((x0, y0), (x1, y1)) => {
    switch (Pervasives.compare(x0, x1)) {
      | 0 => Pervasives.compare(y0, y1)
      | c => c
    }
  };
};

module PairsSet = Set.Make(IntPairs);

let m = PairsSet.(empty |> add((2, 3)) |> add((5, 7)) |> add((11, 13)));
Js.log(PairsSet.min_elt(m)); /* => [2, 3] */
```
- これをコンパイルすると、

---

```js
'use strict';

var $$Set = require("bs-platform/lib/js/set.js");
var Curry = require("bs-platform/lib/js/curry.js");
var Caml_obj = require("bs-platform/lib/js/caml_obj.js");

function compare(param, param$1) {
  var c = Caml_obj.caml_compare(param[0], param$1[0]);
  if (c !== 0) {
    return c;
  } else {
    return Caml_obj.caml_compare(param[1], param$1[1]);
  }
}

var IntPairs = /* module */[/* compare */compare];
var PairsSet = $$Set.Make(IntPairs);
var m = Curry._2(PairsSet[/* add */3], /* tuple */[
      11, 
      13  
    ], Curry._2(PairsSet[/* add */3], /* tuple */[
          5,  
          7   
        ], Curry._2(PairsSet[/* add */3], /* tuple */[
              2,  
              3   
            ], PairsSet[/* empty */0])));

console.log(Curry._1(PairsSet[/* min_elt */20], m));

exports.IntPairs = IntPairs;
exports.PairsSet = PairsSet;
exports.m = m;
```

---

# レコード

- イミュータブルなオブジェクト

```ocaml
/* ここの型宣言がないと型推論に失敗してコンパイルエラー */
type r = {
  a: int,
  b: int 
};

let r1 = { 
  a: 1,
  b: 2
};
let r2 = { 
  ...r1,
  a: 3
};

Js.log(r1.a); /* => 1 */
Js.log(r2.a); /* => 3 */
```

---

- オブジェクトではなく、配列として出力される
- イミュータブルという前提があるため最適化がうまく効いていそう

```js
var r2 = /* record */[
  /* a */3,
  /* b */2
];

console.log(1);

console.log(3);

var r1 = /* record */[
  /* a */1,
  /* b */2
];

exports.r1 = r1; 
exports.r2 = r2; 
```

---

# 演算子の独自定義

```ocaml
let (<$>) = (a, b) => a + b;

Js.log(1 <$> 2); /* => 3 */
```
---

# ReasonReact

- ReactのReasonMLバインディング
- Reduxも一部含む
- Routerも一部含む

---

```ocaml
type action =
  | ChangePage(ReasonReact.reactElement);

type state = {
  page: ReasonReact.reactElement,
};

let component = ReasonReact.reducerComponent("App");

let make = (_children) => {
  ...component,
  initialState: () => {
    {page: <TopPage />} 
  },  
  reducer: (action, state) =>
    switch (action) {
    | ChangePage(page) => ReasonReact.Update({...state, page})
    },
  subscriptions: self => [
    Sub(
      () =>
        ReasonReact.Router.watchUrl(url =>
          switch(url.path) {
          | []                 => self.send(ChangePage(<TopPage />))
          | ["hello", message] => self.send(ChangePage(<HelloPage message=message />))
          | _                  => self.send(ChangePage(<ErrorPage />))
          }
        ),
      ReasonReact.Router.unwatchUrl
    )
  ],
  render: self => self.state.page
};
```

---

# テスト

- Jestが動くらしいが試せていない

---

# コレクションライブラリ

- Immutable.re
    - ReasonML純正ライブラリ
    - 実験段階。Immutable.jsのバインディングを利用するよう公式が言っている
    - githubのコミットも半年前ぐらいでストップ
- rationale
    - Ramda.jsリスペクト
    - 最近作られたばかり
    - Haskellライクな独自演算子を定義

---

# 個人的に気になっているところ

- JSXの構文に少し癖がある
    - テキストノードをそのままかけず、`stringToElement`という関数を呼ぶ必要がある
    - `<div>ReasonReact.stringToElement("Hello")</div>`
      とすると`>`の後ろにスペースがなく構文エラー
- やっぱりOCaml知らないとつらそう
- ~~あまり流行ってない~~
- `ファイル === モジュール`という考え方
    - ディレクトリ構造は無視されてフラットに外部ファイルが呼べてしまう
    - importみたいな構文なく、ファイル名をモジュール名として使ってアクセスできてしまう