PureScriptのInt型って何だろう?
最近、PureScriptという言語の勉強を始めました。PureScriptというのはJavaSciptエンジン上で動作する関数型言語です。haskellライクな文法で記述することができ、コンパイルするとJavaScriptのプログラムコードが生成されて、JavaScriptエンジン上で動作するという仕組みになっています。
JavaScriptのコードに変換されるので、JavaScriptで記述することができない機能は、当然、PureScriptでも記述できません。幸いにも、JavaScriptの関数は、関数オブジェクトと呼ばれる第一級オブジェクトなので、関数型アルゴリズムを記述することができます。そのおかげで、PureScriptという関数型言語が成立しているのです。
また、JavaScriptでプリミティブ型とされるデータ型の多くは、PureScriptでもプリミティブ型のように扱うことができます。Boolean型やNumber型といったPureScriptのプリミティブ型は、そのままJavaScriptのプリミティブ型に対応づけられてコンパイルされます。
ところで、PureScriptにはInt型と呼ばれる見慣れない型が登場します。C言語やJavaといった言語を使い慣れている人にとっては、Int型という言葉は聞き慣れた言葉だと思うのですが、JavaScriptでは整数も小数もNumber型と呼ばれる共通の型で扱います。つまり、JavaScriptのプリミティブ型にInt型などというものはありません。
これは一体どういうことなのでしょう。JavaScriptにプリミティブ型として存在しないにも関わらず、PureScriptではプリミティブ型として扱うことができるInt型とは何者なのでしょうか?
Int型変数は如何にしてJavaScriptに変換されるのか?
この疑問の答えを確かめるために、次のような簡単な関数を書いてみました。関数testInt
は、Int型の引数 a
b
c
を取り、 $a \times b + c$ を計算して、その結果をInt型で返却する関数です。
testInt :: Int -> Int -> Int -> Int
testInt a b c = a * b + c
この関数をコンパイルして、生成されたJavaScriptのコードを見てみれば、疑問の答えが見つかりそうです。実際にコンパイルした結果のJavaScriptを見ると、次のようになっていました。
var testInt = function (a) {
return function (b) {
return function (c) {
return (a * b | 0) + c | 0;
};
};
};
ん???
|0
って何だ???
見慣れない記述がでてきました。普段、JavaScriptを書いている時には使ったことのない演算子|
とそれに続く0
。どうやら、ここにナゾを解くヒントがありそうです。
JavaScriptのビット演算ってどうやるの?
そういえば
|
ってどこかで見たことあるな。そうそう、C言語で出てくるビット演算子と同じだ。
あれ?Number型のビット演算ってどうやるんだ?
謎の記述|0
について、あれこれ考えてみましたが、よく分かりませんでした。そもそも、Number型に対してビット演算をどう施せばいいのか想像がつきません。
ヒントはこちらのブログに書いてありました。
解説によると、JavaScript以外の多くの言語では、そもそも小数を表現する型に対してビット演算は定義されていません。JavaScriptのNumber型は内部的には、倍精度浮動小数点型と呼ばれるC言語のdouble型に相当する型で扱われています。なので、普通に考えればNumber型にビット演算という概念を持ち込むことは無理があるはずです。
普通であればNumber型に対して自然な定義をすることができないはずのビット演算。それがJavaScriptでは定義されている。つまり、普通ではないということです。その普通でない仕様についても、参照先のブログで解説してありました。
解説を読み進めていくと、驚くことが書かれていました。それは「JavaScriptでのビット演算は内部的に32ビット整数型に暗黙変換されてから実施される」ということです。
何だとっ !!!
これで、納得がいきました。普通なら自然に定義することができないはずのビット演算ですが、整数型に変換してからであれば自然に定義することができます。
それともう1つ、ここから驚くようなことが読み取れます。なんと、JavaScriptには、32ビット整数型という隠しデータ型が存在していたのです。このデータ型を仮にInt32型と呼びましょう。
隠しデータ型と言ったのは、このInt32型の変数はJavaScriptから直接生成したり参照したりできないデータ型だからです。しかしながら、確かに演算の過程でInt32型の変数にデータが格納される瞬間があるということです。そして、このInt32型こそPureScriptのInt型の正体だったのです。
PureScriptのInt型は如何にして実現されているか
やっと疑問が解決しました。改めてPursuitを見ると、確かにNumber型はIEEE 754の倍精度浮動小数点型、Int型は32ビット符号付整数型であると書いてあります。
答えがわかったところで、先ほどのPureScriptから生成されたJavaScriptコードを見返してみましょう。
return (a * b | 0) + c | 0;
ここで、a
b
c
自体は倍精度浮動小数点型であるのですが、|0
を加えることで、演算の度にInt32型に引き戻していることがわかります。このギミックを付け加えることにより変数a
b
c
は、実態はNumber型でありながら、整数としての性質だけが残される変数となるのです。
ちなみに、Number型からInt32型への暗黙変換はMath.trunc
と同じ仕様で変換されているようです。つまり、0に近い方へ切り捨てられます。これにより負の整数も不自然なく扱えるようになっているみたいです。
PureScriptから学ぶJavaScript
PureScriptの勉強をしていたら、いつのまにかJavaScriptの勉強にすり替わっていました。ここで学んだ|0
はJavaScriptのコードを書く時にも有用そうです。
普通に可読性を考えたらMath.trunc
使えよってところですが、これだと文字数が多いので、式中に何度も出てくると却って可読性が低下するような場面も見受けられます。|0
というセットの記号で、演算結果を整数に引き戻す後置演算子だと思えば、整数演算をシンプルに書くことができます。
一般にJavaScriptの数値演算は、double型の丸め誤差が要因となって、正確ではないことが知られています。例えば、0.01 + 0.05
を計算すると0.060000000000000005
になったりします。なので、最終的に整数になることが期待される計算であっても、期待するような整数になってくれないことが多発します。|0
はそんな時のテクニックとして役立ちそうです。
$ node -e "console.log(100 * (0.01 + 0.05))"
6.000000000000001
$ node -e "console.log(100 * (0.01 + 0.05) | 0)"
6
今回は、素朴な疑問から始まって調べてみたら、JavaScriptの深淵を覗くような知識を得ることができました。
普段あまり意識することのない自動生成コードですが、実際に読んでみると、PureScriptの言語としての仕組みがよく理解できると同時に、JavaScriptの勉強にもなります。特に、関数型言語は独特の概念がでてくるので、「手続き型に言い直すとどういうことだろう」という疑問に自動生成コードは答えてくれます。
おわりに
PureScriptの情報って他の言語と比べると少ないですよね。困った時はhaskellやJavaScriptのドキュメントを調べて参考にすることが多いです。
日本語情報も少ないですが、公式のドキュメントも、あまり充実しているとは言えない感じがします。できるだけたくさん書いて、PureScriptファンが増えると嬉しいです。
今は、PureScriptについて勉強しながら、理解を深めるために色々と書きためています。書けたら順次公開していきたいと思っているので、しばらくお待ちください。