Posted at

ReasonML雑感

More than 1 year has passed since last update.


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://github.com/facebook/reason/tree/master/src より引用



構文


  • パターンマッチ

  • ファンクター(Module Functions)

  • パイプ演算子

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] */


  • これをコンパイルすると、


'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;



レコード


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

/* ここの型宣言がないと型推論に失敗してコンパイルエラー */

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 */



  • オブジェクトではなく、配列として出力される

  • イミュータブルという前提があるため最適化がうまく効いていそう

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;



演算子の独自定義

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

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



ReasonReact


  • ReactのReasonMLバインディング

  • Reduxも一部含む

  • Routerも一部含む


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みたいな構文なく、ファイル名をモジュール名として使ってアクセスできてしまう