型のビット表現
Elmにはさまざまな種類の型があります。
Bool
Int
String
(Int, Int)
Maybe Int
- ...
すでに私たちはこれらの型を概念としては理解していますが、コンピュータはこれをどのように理解しているのでしょうか? Maybe Int
はハードディスクにどのように格納されているのでしょうか?
ビット列
ビットとは、0または1のどちらかの状態をとる、小さな箱のようなものです。コンピュータのメモリは、このビットがとてつもなく長く連なったものです。
コンピューターのメモリはビットで表現されているのだから、ビットだけを使ってありとあらゆる値を表現しなければなりません!
Bool
型
Bool
はTrue
またはFalse
のどちらかであるような値のことです。これはちょうどビットにそのまま対応していますね!
Int
型
Int
型の値とは、0
や1
、2
などの数のことです。単一のビットでこれを表すことはできませんから、複数個のビットを使う以外に選択肢はありません。通常は、Int
は次のようなビットの列になっています。
00000000
00000001
00000010
00000011
...
これらのビット列について、自由に数を割り当てることができます。たとえば00000000
は0、00000001
は1というようにです。素晴らしいですね! あとは数をビット列に昇順に割り当てていくだけですが、そのうちこれらのビット列では足りなくなってしまうでしょう……。
単純計算では、8ビットでは (2^8 = 256) までの数しか表すことができません。9000や8004のような数ももちろん数に違いないですが、これらを表現するにはどうすればいいのでしょうか?
その答えとは、さらにビットを足すだけです。長年、人々は 32ビット数を使ってきました。32ビット数は人間が通常扱うような (2^32 = 4,294,967,296) までの数を表現することができますが、今日のコンピュータは (2^64 = 18,446,744,073,709,551,616) までの数を表現できる64ビット整数をサポートしています。これはまたずいぶんと多いですね!
Note: もし更に詳しく知りたいのなら、2の補数について学んでみてください。決して気まぐれに数にビット列を割り当てているわけではないことがわかると思います。加算をなるべく高速に実行するために、ある規則に従って巧妙に数を割り当てているのです。
String
型
文字列"abc"
はa
b
c
という文字が順番に並んだものですから、まずは文字をビット列で表現することから始めましょう。
文字を符号化する方法としてもっとも初期からあるものに、ASCIIと呼ばれるものがあります。ちょうど数と同じように、大量のビット列を並べて、その先頭から値を自由に割り当てていきます。
00000000
00000001
00000010
00000011
...
ここでどの文字も8ビットに収まるようにする必要がありますが、それはつまり256文字までしか表現できないということです! 英語だけを考慮するなら、これはなかなかうまくいきます。26個の小文字と26文字の大文字、10個の数だけをカバーできればいいのですから、ぜんぶで62文字になります。残りの空いている部分は、記号や他のいろんな変なものに割り当てられます。結局どのようになっているのかは、ここで詳しく見ることができます。
これで文字をどのように表現すればいいのかはわかってきましたが、String
の終端と次のデータの開始がどこにあるのかはどのようにすればわかるのでしょうか? すべてはただのビット列なのです。実際には文字もInt
値とまったく同じようにしか見えないのです! そのため、どこが終端なのかを見分ける何らかの方法が必要です。
最近では、文字列の長さを格納するようにする言語が多いです。"hello"
のような文字列は、メモリ内では5
h
e
l
l
o
のように見えるでしょう。String
は常に長さの32ビット表現で始まるということがわかると思います。そして、文字列の長さが0であろうと9000であろうと、文字列の終端がどこにあるのかを正確に知ることができます。
Note: 中には、英語以外の言語をカバーしたいという人もいるでしょう。これはUTF-8という符号化に従うことで実現することができます。これは本当に素晴らしいものですので、ぜひ学んでみることをお勧めします。この符号化では、たとえばある文字列の「5番目の文字」を見つけるようなことは思ったより難しいということがわかると思います!
(Int, Int)
型
タプルについてはどうでしょうか? (Int, Int)
はふたつのInt
値で、そのそれぞれがビット列になるのでした。メモリ内でふたつのビット列を並べて配置すれば、それで出来上がりですね!
カスタム型
カスタム型とはつまり、異なる型どうしを組み合わせです。異なる型はそれぞれ異なった構造をしているでしょう。まずは次のようなColor
型について考えてみます。
type Color = Red | Yellow | Green
Red = 0
、Yellow = 1
、Green = 2
というように、それぞれの場合について数を割り当てることができます。ここでInt
の表現をそのまま使うことができます。ここですべての場合を網羅するには2ビットあれば十分で、00
は赤、01
は黄色、10
は緑、11
は未使用とすればいいでしょう。
しかし、Maybe Int
のような、追加のデータを持つカスタム型についてはどうでしょうか? 典型的な方法としては、それぞれのビット列に「タグ」を付けるというものがあります。たとえば、Nothing = 0
やJust = 1
というように取り決めることができ、例を挙げると次のようになります。
Nothing
=0
Just 12
=1
00001100
Just 16
=1
00010000
case
式ではこの「タグ」を見ることで、次に何をするかを決めています。もしタグが0
なら、それ以上データはないことがわかります。もしタグが1
なら、データを表すビット列がその後ろに続くことがわかります。
この「タグ」のアイデアは、String
の値の最初にその文字列の長さを置くのと似ています。サイズは値によってそれぞれ異なっているかもしれませんが、どこから始まりどこで終わるのかはコードからいつでも把握することができるのです。
まとめ
これで、あらゆる値をビット列で表現する方法がわかりました。このページでは、データ型が内部ではどのように表現されているのかを大まかに説明してみました。
通常は型のビット表現について考えなくてもいいのですが、カスタム型やcase
式についての自分の理解を深めていくのに便利なのです。この知識が何かの役に立つことを願っています!
Note: もしこれに興味を持ったのなら、ガーベージコレクションについても学んでみると面白いかもしれません。この話題についてはThe Garbage Collection Handbookが優れた資料だと思います!