BuckleScriptに入門しました

# BuckleScriptに入門しました #
最近、フロントエンドを久しぶりに書く必要に駆られたのですが、その際に前から気になっていた [BuckleScript](https://github.com/BuckleScript/bucklescript) を触ってみて入門してみました。

## Bucklescriptとは ##
公式的には

> A backend for the OCaml compiler which emits JavaScript. 

とのことです。大元は、Bloomberg社（JaneStreetと同じく？OCamlの大口ユーザー）で作成されていたものだそうです。特徴として

* 型安全（OCamlだからね！）
* ハイクォリティなdead code elimination
  * OCamlからの生成時のみならず、bundler（Google closure-compilerとか）でもdead code eliminationがやりやすいようなソースを出すそうです
* Offline optimization
  * 大抵のJavaScript処理系が持つJITを頼らずに、高速なコードを生成するそうです
* JS/ネイティブ両方対応
  * JSってどこでも動くよね、ということで。
* OCamlコンパイラを利用することでの（文字通りに）桁違いの速度のコンパイル
  * TypeScriptの速度と比べると、文字通りに桁違いに速いです。
  * 全部合わせて60ファイルくらいのコンパイルが、1.2秒（実測）で終わるくらい速いです。

ということが、公式ドキュメントで語られています。

ただ、いくつか注意があります。

* 元になっているOCamlのバージョンが4.02.3
  * Bucklescriptが、いくつかの構文拡張などのために、OCamlコンパイラ自体にパッチを当てているためです。常に最新のバージョンを利用している人は注意です。

## Bucklescriptのインストール ##

インストールは非常に簡単で、OCamlユーザーであれば使っているOPAMであれば、Bucklescriptのパッチが当たった版のOCamlをお手軽にインストールすることが出来ます。

```shell
opam switch 4.02.3+buckle-master
```

Bucklescriptを実際に利用する時は、 ``bs-platform`` というnpmパッケージを利用します。

```shell
npm install --save bs-platform
```

Bucklescriptの環境は、bs-platformが提供するコマンドから作成できます。

```shell
$ $(npm bin)/bsb -init test
$ ls -la
合計 36
drwxr-xr-x 5 derui derui 4096  7月 18 22:40 .
drwxr-xr-x 3 derui derui 4096  7月 18 22:40 ..
-rw-r--r-- 1 derui derui  168  7月 18 22:40 .gitignore
drwxr-xr-x 2 derui derui 4096  7月 18 22:40 .vscode
-rw-r--r-- 1 derui derui  151  7月 18 22:40 README.md
-rw-r--r-- 1 derui derui  141  7月 18 22:40 bsconfig.json
drwxr-xr-x 3 derui derui 4096  7月 18 22:40 node_modules
-rw-r--r-- 1 derui derui  276  7月 18 22:40 package.json
drwxr-xr-x 2 derui derui 4096  7月 18 22:40 src
```

見慣れたnpmの構成に加えて、 ``bsconfig.json`` というファイルがあります。これが、Bucklescriptでのビルド指示を行うファイルです。中身はこんな感じになっています。

```json
{
  "name": "main",
  "version": "0.1.0",
  "sources": [
    {"dir": "src", "public": "all"}
  ],
  "bs-dependencies" : [
    "bs-fetch", "bs-dom-wrapper", "bs-lwt"
  ],

  "generate-merlin": true,
  "package-specs": ["commonjs"]
}
```

安心のmerlin対応です。また、 ``bs-dependencies`` という見慣れないものがあります。Bucklescriptの大きな特徴は、パッケージ管理としてnpmをそのまま利用できる点で、npmでインストールしたパッケージをこっちにも書くことで、OCaml内からも利用できます。

## Bucklescriptの例 ##
実際に書いた、すごい薄いReactのバインディングはこんな感じです。前半はglueコードなので、bs.raw extensionが終わった後からが本番です。

```ocaml
[%%bs.raw{|
var _React = require('react');
var _createReactClass = require('create-react-class');

function _createElement (clazz, props, children) {
  return _React.createElement(clazz, props, ...children);
}

function _createClass (fn, initialState, config) {
  return _createReactClass({
    getInitialState: function () {
      return { state: initialState };
    },

    componentWillReceiveProps: function(newProps) {
      if (config && config.willReceiveProps) {
        config.willReceiveProps(this.props, this.state.state, newProps, state => this.setState({state}));
      }
    },

    shouldComponentUpdate: function(props, state) {
      if (config && config.shouldUpdate) {
        return config.shouldUpdate(this.props, this.state.state, props, state.state);
      }
      return true;
    },

    componentDidUpdate: function() {
      if (config && config.didUpdate) {
        config.didUpdate(this.props, this.state.state, state => this.setState({state}));
      }
    },

    componentDidMount: function() {
      if (config && config.didMount) {
        return config.didMount(this.props, this.state.state, state => this.setState({state}));
      }
    },

    componentWillMount: function() {
      if (config && config.willMount) {
        return config.willMount(this.props, this.state.state, state => this.setState({state}));
      }
    },

    componentWillUnmount: function() {
      if (config && config.willUnmount) {
        return config.willUnmount(this.props, this.state.state);
      }
    },

    render: function () {
      return fn(this.props, this.state.state, state => this.setState({ state }))
    }
  });
}
|}]

module D = Bs_dom_wrapper

type element
type ('props, 'state) component
type 'state set_state_fn = 'state -> unit

type ('prop, 'state) should_update =
  'prop -> 'state -> 'prop -> 'state -> bool
type ('prop, 'state) mount = 'prop -> 'state -> 'state set_state_fn -> unit
type ('prop, 'state) unmount = 'prop -> 'state -> unit
type ('prop, 'state) receive_props = 'prop -> 'state -> 'prop -> 'state set_state_fn -> unit

(* make configuration object for component created from createComponent_ function *)
external make_class_config :
  ?shouldUpdate:('prop, 'state) should_update ->
  ?didUpdate:('prop, 'state) mount ->
  ?willReceiveProps:('prop, 'state) receive_props ->
  ?didMount:('prop, 'state) mount ->
  ?willMount:('prop, 'state) mount ->
  ?willUnmount:('prop, 'state) unmount ->
  unit -> _ = "" [@@bs.obj]

type ('props, 'state) render_fn = 'props -> 'state -> 'state set_state_fn -> element
external createComponent_ : ('props, 'state) render_fn -> 'state -> 'a Js.t -> ('props, 'state) component = "_createClass" [@@bs.val]

external createComponentElement_ : ('props, 'state) component -> 'props -> element array -> element = "_createElement" [@@bs.val]
external createBasicElement_ : string -> 'a Js.t -> element array -> element = "_createElement" [@@bs.val]

(* Needed so that we include strings and elements as children *)
external text : string -> element = "%identity"

(*
 * We have to do this indirection so that BS exports them and can re-import them
 * as known symbols. This is less than ideal.
 *)
let createComponent = createComponent_
let element = createBasicElement_

(* Event of React *)
module SyntheticEvent = struct
  class type ['a, 'b] _t =
    object
      method preventDefault: unit -> unit
      method stopPropagation: unit -> unit
      method bubbles: bool
      method cancelable: bool
      method currentTarget: 'a Dom.htmlElement_like
      method defaultPrevented: bool
      method eventPhase: int
      method isTrusted: bool
      method nativeEvent: 'b Dom.event_like
      method isDefaultPrevented: unit -> bool
      method isPropagationStopped: unit -> bool
      method target: 'a Dom.htmlElement_like
      method timeStamp: int
      method type_: string

      (* properties when event belongs Mouse Events *)
      method altKey: bool
      method button: int
      method buttons: int
      method clientX: int
      method clientY: int
      method ctrlKey: bool
      method getModifierState: int -> bool
      method metaKey: bool
      method pageX: int
      method pageY: int
      method relatedTarget: 'a Dom.htmlElement_like
      method screenX: int
      method screenY: int
      method shiftKey: bool

    end [@bs]
  type ('a, 'b) t = ('a, 'b) _t Js.t
end

(* Define common prop object. *)
external props :
  ?className: string ->
  ?onClick:(('a, 'b) SyntheticEvent.t -> unit) ->
  ?onChange:(('a, 'b) SyntheticEvent.t -> unit) ->
  ?onSubmit:(('a, 'b) SyntheticEvent.t -> unit) ->
  ?href:    string ->
  ?_type:   string ->
  ?value:   string ->
  ?defaultValue: string ->
  unit -> _ =
  "" [@@bs.obj]

(* Ignore function currying with external function *)
let div props children = createBasicElement_ "div" props children
let span props children = createBasicElement_ "span" props children
let a props children = createBasicElement_ "a" props children
let button props children = createBasicElement_ "button" props children
let input props children = createBasicElement_ "input" props children
let form props children = createBasicElement_ "form" props children
let label props children = createBasicElement_ "label" props children
let p props children = createBasicElement_ "p" props children
let canvas props children = createBasicElement_ "canvas" props children
let img props children = createBasicElement_ "img" props children

let component comp = createComponentElement_ comp

(* -- *)

external render : element -> 'a Dom.node_like -> unit = "" [@@bs.module "react-dom"]

```

JavaScript側の関数に対するバインディングは、OCaml公式のexternal（大抵はCとかのバインディングで使う）を利用するため、構文的に矛盾がありません。すでにCamlP4ではなく、PPXが推奨されている時に開発されたものなので、怪しげな構文拡張とかは基本的になく、attributeとextensionだけ（とちょっとだけ拡張されたclass構文）で構成されています。

上のバインディングを使うと、例えばinputはこんな感じに書けます。

```ocaml
module R = React

(* Property for file component *)
type prop = {
  state: Reducer.state;
  dispatcher: Dispatch.t;
}

external form_prop :
  ?className: string ->
  ?onSubmit: (('a, 'b) R.SyntheticEvent.t -> unit) ->
  unit -> _ = "" [@@bs.obj]

type state = unit

let on_submit prop e =
  e##preventDefault ();
  let dispatch action = Dispatch.dispatch prop.dispatcher action in 
  Actions.upload_image dispatch prop.state.Reducer.stripped_image

let render props _ _ =
  R.form (form_prop ~className:"tp-ImageUploader" ~onSubmit:(on_submit props) ()) [|
      R.input (R.props ~className:"tp-ImageUploader_Input" ~onChange:(fun _ -> ()) ()) [||];
    |]

let t = R.createComponent render () (React.make_class_config ())
```

TypeScriptとかで頑張ってGenericsを使って書いているReactのprops/stateとかも、OCamlのレコードとかで利用できますし、ReduxのAction/Reducerとかとの相性は抜群です。

特に、Actionが代数データ型で定義できるのが非常に楽、というか自然に対応できます。対処していないActionがあれば警告、削除漏れがあればエラーなど、強力なコンパイラにまかせて楽が出来ます。

また、 ``bsb -init`` で作成した環境は、 ``npm run watch`` でビルドのポーリングが可能になっており、超高速なコンパイルと相まって生産性も高いです。（人によって感じ方には差異があります）

## Bucklescriptのtips ##
tipsがあるほど書いてませんが、それでもいくつかコツ的なものがあったので紹介します。

### class typeとexternal ###
JSといえば object、というくらい頻出するObjectですが、Objectとのバインディングを行う方法もかなりの量があります。

そのうちでも頻出する（と思っている）のが、class typeによるObject型の定義と、externalによるObject型の作成です。

class typeによるObject型の定義は、公式からの引用ですが、以下のようになっています。

http://bucklescript.github.io/bucklescript/Manual.html#_complex_object_type

```ocaml
class type _rect = object
  method height : int
  method width : int
  method draw : unit -> unit
end [@bs] (1)
type rect = _rect Js.t
```

この型がついた変数は、普通にmethodを利用するのと似た拡張構文である ``##`` を利用することで、JavaScriptのオブジェクトそのままに扱うことが出来ます。class typeを用いていることで、structure typingが可能となっています。

class typeを使ったObject型の定義は、 **JavaScriptから返されるオブジェクト** に対して利用するのがオススメです。頻繁に利用すると、OCaml側のobject型に慣れていないと？？ってなる型エラーがそこかしこで出ます。（経験済み）

対して、 externalによるJavaScriptのObject作成は、 **OCamlからJavaScriptの関数に渡すオブジェクト** に対して利用するのがオススメです。これまた公式の例ですが、次のように標準のラベル変数を利用することで、さっくり作成できます。

```ocaml
external make_config : hi:int -> ?lo:int -> unit -> t = "" [@@bs.obj] (1)
let u = make_config ~hi:3 ()
let v = make_config ~lo:2 ~hi:3 ()
```

バインディングを書く対象のライブラリにも寄ると思いますが、JavaScript側のライブラリでなんやかややるタイプの場合はexternalが頻出して、API呼び出しとかでJSONが返ってくるとかそういった場合にはclass typeを使う、みたいな感じでした。

### モジュールの分け方 ###
OCamlと同じです。以上。

だとなんともならないんですが、OCamlを利用している以上、NodeJSでやっているような、非常に細かいモジュール分けや、Reactに見られるようなネストしたモジュール構造をOCamlで再現するのはすごーく辛いです。プロジェクト全体でファイル名自体がユニークでなければならないという制約があるので、実際の利用ではここが一番のネックではないでしょうか。

ですので、型に頼りたいロジック側の処理だけBucklescriptで、それ以外はJavaScriptで普通に書く、というのも手です。実際にReact部分も含めて全てBucklescriptで書いてみた限りでは、むしろReact側の方が型に頼りたい感じがするくらいでした。
props/stateが型に守られていると、あの値入れるの忘れた！とかなんでpropに入ってないんだ・・・的なケアレスミスとか単純でもめんどくさいミスが激減しました。

## Bucklescriptはいいぞ ##
計一週間くらい書いてみましたが、バインディングさえ作成してしまえば、後はサクサク書いていくことが出来ました。今回はJavaScript側のライブラリはReactだけで、Redux的なものは自作してみましたが、moduleの作成とかfunctorのケアレスミスに悩んだくらいで、実装自体に苦労はしませんでした。

Bucklescriptもそうですが、Facebookの [Reason](https://github.com/facebook/reason) とか、最近はフロントエンドでOCamlが熱いようです。これを機にユーザーが増え・・・ないかもしれませんが、OCamlの裾野が広がればいいなーと思います。

個人的に作成するJavaScriptは基本的にBucklescriptで書いていこうかな、と思える程度には使えるものになっていますので、ぜひ触ってみてください。