Posted at

BuckleScript BeltをReasonMLから使ってみた

More than 1 year has passed since last update.


Belt

BeltというBuckleScriptの標準ライブラリが開発されています。

https://bucklescript.github.io/bucklescript/api/Belt.html

(2018/04/09時点では、ベータ版です)

もちろんReasonMLのバックエンドはBuckleScriptなので、ReasonMLから利用することができます!

ReasonMLから触ってみたので、簡単なまとめと感想をメモしておきたいと思います。

各APIの使用方法については細かく触れずに、おおまかな方向性だけをまとめます。


構成モジュール


  • Id


  • Array

  • SortArray

  • MutableQueue

  • MutableStack

  • List

  • Range

  • Set

  • Map

  • MutableSet

  • MutableMap

  • HashSet

  • HashMap

  • Option

といったモジュールが用意されています。

コレクション操作ライブラリとして一通りの機能は有している印象です。

Idだけ少し浮いていますが、こちらはSetやMapを利用するときに必要です。

今回触ったのは、Id, Array, List, Set, Map, Optionです。


命名規則

命名規則で、挙動をある程度明示しています。



  • ****Exnは例外を投げる関数


  • ****Uはカリー化されていない関数

2018/04/09時点のReasonMLのドキュメントには、カリー化されていない関数の扱いがなかったので、こちらにメモしておきます。

引数の前にピリオドをつけることで、アンカリー化できるようです。

let compact = ary => Belt.List.keepU(ary, (. elm) => elm != None);

ちなみにBuckleScriptのドキュメントにはReasonMLのシンタックスについて記載されています。https://bucklescript.github.io/docs/en/function.html#solution-guaranteed-uncurrying


引数の順序

OCaml/ReasonMLではカリー化を前提としているためか、モジュールの対象となる型の引数は、末尾に配置されます。

例えば、List.mapは以下のように使います。

List.map((elm) => elm * elm, [1, 2, 3]);

一方で、BeltはJavaScriptとの親和性を追求したため、モジュールの対象となる型の引数は、先頭に配置されます。

Belt.List.map([1, 2, 3], (elm) => elm * elm);

これにより、ひとつの問題を抱えることになります。


パイプオペレータ

OCaml/ReasonMLにもelixirのようなパイプオペレータがあります。

定義は、以下です。

let (|>) = (a, f) => f(a);

ここで重要なのが、単に関数に値を適用しているだけということです。

elixirでは第一引数に値を適用しますが、ReasonMLの場合、すでに部分適用された関数に、さらに値を適用をするだけです。

[1, 2, 3]

|> List.map((elm) => elm * elm);

/* "これは以下とおなじ" */
let f = List.map((elm) => elm * elm);
f([1, 2, 3]);

これをBeltのListに対して同じことをすると型エラーになります。

/* "Belt.List.mapの第一引数の型はListなので、コンパイルエラー" */

[1, 2, 3]
|> Belt.List.map((elm) => elm * elm);

ちゃんと解決策が用意されています。


|. パイプオペレータ

|.は、第一引数に値を適用する新しいパイプオペレータです。

以下のように使用します。

[1, 2, 3]

|. Belt.List.map((elm) => elm * elm);

もうひとつの解決策が、_の利用です。

_を関数に渡すと、その引数への部分適用をスキップすることができます。

以下のように通常のパイプオペレータのままでBelt.List.mapを使用できます。

[1, 2, 3]

|> Belt.List.map(_, (elm) => elm * elm));


感想(というか欲しいモノ)


List.range

細かい話ですが、List.rangeほしいです。

Array.rangeはあるので、そのうち入れてくれると信じてます。

いまは、Array.rangeとList.fromArrayを組み合わせて、下記で生成しています。

Array.rangeBy(0, 10, ~step=2) |. List.fromArray;

これに限らず、List,Array,Setが似たような構造なのに、こっちにはあって、こっちにはないということが結構あります。

実装するのが困難な関数もあるとは思いますが、単純に優先度が低いのかもしれません。

(なんとなく、ListよりArrayが優遇されている気がします)


型変換

ListからArray、ArrayからSetなどの変換が用意されています。

ただ、ListからSetが用意されていなかったり、微妙に不便です。

また、ListからArrayに変換するときも、ListのモジュールでやるのかArrayのモジュールでやるのか、よく混乱します。

混乱を避けるために、変換用のモジュールがあってもいい気がしました。


標準ライブラリの統一

現状は、ReasonMLのAPI、BeltのAPIを見比べたりして、かなり疲れます。

このあたり悩む必要がなくなると、使う側は嬉しいはずです。

ReasonMLの標準ライブラリでできることは、Beltで代替手段があると嬉しいです。

例えば、StreamはReasonMLの標準ライブラリにあって、Beltにはありません。

逆に、OptionはBeltにありますが、ReasonMLの標準ライブラリにはありません。


サンプルコード

ReasonMLの標準ライブラリは、型と説明があるだけで、どのように使えるかのサンプルがありません。

Beltのドキュメントもサンプルコードが少ないので、このあたりが強化されると、ユーザーは使いやすいと思います。

また、BeltはBuckleScript由来なので、サンプルコードがあってもOCamlの構文です。


まとめ

BuckleScriptは、かなり最適化に力を入れている印象があります。

FacebookもprepackでJavaScriptの最適化に対して積極的なので、ReasonMLを使うことがフロントエンドのパフォーマンス問題の一つの解になりそうです。

Beltもそういった中で生まれたように思います。

まだベータですが、今後も動向を追っていこうと思いました。