検索
連載

BuckleScript(OCaml→JavaScriptコンパイラ)Dev Basics/Keyword

BuckleScriptはJavaScriptコードを生成するOCamlコンパイラ。OCamlで型安全なプログラミングを行い、そこから可読性の高いJavaScriptコードを生成できる。

Share
Tweet
LINE
Hatena
「Dev Basics/Keyword」のインデックス

連載目次

 BuckleScriptは、JavaScriptコードを生成するOCamlコンパイラだ。ブルームバーグが開発し、オープンソースソフトウェアとして公開している(BuckleScriptはOCamlをJavaScriptに変換するコンパイラプロジェクトである「js_of_ocaml」から派生したプロジェクト)。

JavaScriptとOCaml

 JavaScriptの活用範囲は既にWebブラウザを飛び出して、サーバサイドやモバイルアプリ、デスクトップアプリにまで広がっている。そして、多くのPCやデバイスには何らかの形でJavaScriptエンジンが搭載されている。さらに、JavaScript実行環境間での互換性も高まりつつある。すなわち、JavaScriptは多くの環境で同様に動作するプラットフォームとなっている。OCamlコードをJavaScriptコードにコンパイル(トランスパイル)できれば、JavaScriptがサポートされている環境(=ほぼ全ての環境)でOCamlプログラムを実行できるようになるのは魅力的なところだ。

 その一方で、JavaScriptは柔軟な記述が可能な動的型付け言語であり、大規模開発には向いていないといわれている。この点を埋めるべくTypeScriptでは、静的な型注釈を付加し、JavaScriptコードへのトランスパイル時に静的な型チェックを行うことで、JavaScriptベースの言語であっても大規模開発を行えるようにしている。

 BuckleScriptはこうした流れの中で生まれたものだといえる。ただし、そこで使われる言語がOCamlというのが大きな違いである。OCamlは静的型付けの関数型言語であり、強力な型推論機能を持っているのが大きな特徴だ。コンパイル時には静的解析が行われた後は、実行中に何らかのタイプエラーが発生することはない。さらに簡潔な記述が可能であることも相まって、多くの企業で実際に利用されている実績のある言語だ(BuckleScriptを開発しているブルームバーグ自体がOCamlのユーザーとなっている)。

 「BuckleScript User Manual」ページでは、BuckleScriptの特徴として以下が挙げられている。

  • 型安全
  • デッドコードの削除
  • コンパイル時の最適化
  • ネイティブコードへのコンパイル
  • 高速なコンパイル
  • 可読性が高くコンパクトなJavaScriptコードを出力
  • 元のコードの構造を維持

 これらの多くは、OCamlが持つ型システムとコンパイラチェーンによる恩恵だ。例えば、コンパイル時の静的解析により、関数やモジュールレベルでデッドコード(呼び出されることがないコード)が検出/削除される。これはJavaScriptコードの実行時にVMが無駄なコードを評価することがなくなると同時に、出力されるコードがコンパクトになることを意味している。

BuckleScriptを使ってみる

 BuckleScriptを試してみるにはWebブラウザを使うのが一番簡単だ。ここでは「Playground」ページで実際にこれを試してみよう。

BuckleScriptのPlaygroundページ
BuckleScriptのPlaygroundページ

 Playgroundページを開くと以下のコードが左側のペーンに、そのコンパイル結果が右側のペーンに表示されている。

print_endline "Hello BuckleScript!"


OCamlのHello Worldプログラム

 元のコードもコンパイル結果もコンソールに文字列を出力するだけなので、まずは関数を定義して、それを呼び出すようにしてみよう。

let hello x = print_endline ("Hello " ^ x)
let () = hello "World"


hello関数

 2行目の「let () = ……」はこのスクリプトのエントリポイントとなり、"World"を引数としてhello関数を呼び出している。コンパイル結果は次のようになる。

'use strict';

function hello(x) {
  console.log("Hello " + x);
  return /* () */0;
}

console.log("Hello World");

exports.hello = hello;

コンパイル結果

 hello関数が等価なJavaScriptコードになっているのが確認できる。が、ここで注意したいのはconsole.logメソッド呼び出しが2つある点だ。そして、2つ目のconsole.logメソッド呼び出しでは引数が「"Hello World"」となっている。つまり、このプログラムを実行しても、hello関数は呼び出されない。これはOCamlコードのコンパイル時に行われた最適化の結果だろう。Playgroundページの右上には[Remove unused code]チェックボックスがあるので、これをオンにすると、JavaScriptコードが表示されているペーンがさらにスッキリしたものになるので、確かに使われていないことが分かる。

使われていないコードを削除
使われていないコードを削除

 次に再帰関数を2つ定義してみる。1つは通常の再帰関数で、もう1つは末尾再帰を行う。

let rec fact n =
  if n = 0 then 1
  else  n * fact(n - 1)
 
let rec fact2(n, count) =
  if n = 0 then count
  else fact2 (n-1, n * count)
 
let () =
  print_endline(string_of_int(fact(4)));
  print_endline(string_of_int(fact2(4, 1)))


2つの再帰関数を定義

 コンパイル後のコードは次のようになる。

'use strict';

var Pervasives = require("stdlib/pervasives");
var Caml_int32 = require("stdlib/caml_int32");

function fact(n) {
  if (n) {
    return Caml_int32.imul(n, fact(n - 1 | 0));
  }
  else {
    return 1;
  }
}

function fact2(_param) {
  while(true) {
    var param = _param;
    var count = param[1];
    var n = param[0];
    if (n) {
      _param = /* tuple */[
        n - 1 | 0,
        Caml_int32.imul(n, count)
      ];
      continue ;
      
    }
    else {
      return count;
    }
  };
}

console.log(Pervasives.string_of_int(fact(4)));

console.log(Pervasives.string_of_int(fact2(/* tuple */[
              4,
              1
            ])));

exports.fact  = fact;
exports.fact2 = fact2;

コンパイル後のJavaScriptコード

 通常の再帰関数は再帰を行うJavaScriptコードに、末尾再帰を行うOCamlコードは展開されてwhileループを使って計算を行うようにコンパイルされたことが分かる(コードの詳細な解説は省略する)。

 ここで注意したいのは、最初にある2つのrequire関数だ。最初にインポートしている「Pervasives」モジュールはOCamlの組み込み型に対する基本操作を定義しているモジュールだ(OCamlのPervasivesモジュールがJavaScriptコードにマッピングされている)。ここでは整数を文字列変換するstring_of_int関数を使用している。また、Caml_int32モジュールには整数の乗除演算など幾つかの演算が定義されており、ここでは32bit整数の乗算を行うCaml_int32.imul関数を利用している。

 2つのコードを比べると、OCamlのコードが極めて簡潔であることと、出力されたJavaScriptコードの可読性の高さが感じられる。また、生成されたコードはJavaScriptモジュールとなっているので、JavaScriptコードから利用可能だ(逆に、OCmalコード内でFFI=Foreign Function Interfaceを使用して、JavaScriptオブジェクトを作成/操作することも可能だ。詳細については「BuckleScript User Manual」を参照されたい)。

 なお、BuckleScriptはnpm経由でローカル環境にインストールすることも可能だ(インストール時にBuckleScriptのビルドが行われるため、Cコンパイラツールチェーンが必要)。


 BuckleScriptは、JavaScriptコードを生成するOCamlコンパイラだ。OCamlが持つ強力な型システムを活用することで、冗長な型注釈を記述することなく、型安全なプログラミングを行い、そこから可読性の高いJavaScriptコードを生成できる。また、本稿では取り上げなかったが、OCamlコードとJavaScriptコードとの相互運用性などもサポートされており、OCamlコードからJavaScriptコードを呼び出したり、その逆を行ったりすることも可能だ。

参考資料

  • BuckleScript: BuckleScriptのリポジトリ
  • BuckleScript User Manual: ユーザーガイド(英語)。BuckleScriptの概要からインストール方法、最初の一歩、OCamlとJavaScriptの相互運用までを網羅している
  • bucklescript-addons: BuckleScriptのサンプルコードのリポジトリ

「Dev Basics/Keyword」のインデックス

Dev Basics/Keyword

Copyright© Digital Advantage Corp. All Rights Reserved.

ページトップに戻る