長い長い長い整数

stdint.h

uint32_tが欲しいんだけど、IntegerはCと同じでレジスタサイズなんだよね。
Long_IntegerとShort_Integerのどっちかな?

Long_IntegerとShort_Integerも環境依存ですってば。

変なとこでCと同じなんだな。
じゃあstdint.hみたいなのはあるかい?

一応InterfacesにInteger_32とUnsigned_32はありますが、そもそもどうしてuint32_tが欲しいんですか?

ちょっとWAVファイルにフィルタをかけたくてね。
まあ探せばフリーソフトもいっぱいあるんだけど、WAVなんて単純だから自分で書いたほうが、細かいところまで好みに調節できるじゃない。これでも昔は凝ってたんだよ。
……と思ったら、最近は24ビットのWAVファイルなんてあるんだよね。8か16しかないと思ってたんだけどなあ。まあそれはいいよ。
符号なし型が無さそうなのが面倒だなと思ってたけど、Unsigned_32なんてあるのか。シフト演算子も無かったけど、ちゃんとShift_LeftやShift_Rightも用意されてるし、こりゃいいや。

それでは必要なのは24ビット整数型ではないでしょうか。

うーん、一度にファイル全体を読み込むから、配列で確保しちゃうと8ビット分、1/4は無駄なメモリになるんだよね。
どうせエンディアンを揃えながら読まないといけないから、メモリマップドファイルにできないとかは別にいいんだけど。
C言語ならポインタ演算で3バイトずつ進めて処理するところだけど、Adaはポインタ演算できないからなあ。
まあ仕方ないよ。

仕方なくないですよ。
24ビット整数の配列を作ればいいじゃないですか。

おいおい、僕のPentiumはまだ32ビット世代のものだぜ。
だいたい64ビットCPUにも24ビット整数命令が追加されたなんて話は聞かないぞ。
もしかしたらそんなCPUもあるのかもしれないけど、僕の持ってないCPUの話をされても使いようがないじゃないか。

いや……

ははあ、また人をからかってるな。
まあいいよ、とにかくUnsigned_32があるのはわかったから、それで組むよ。

人の話を聞きなさい……。

24ビット整数

帰ろうとする氏をなんとか呼び止めました。

僕は早く帰って続きを書きたいんだよ……からかわれてるのではなさそうだけどさ。
もしかしてビットフィールドの配列版みたいなのがあるのかい?

ありますね。
今回の場合ですと、こんな感じかな。

   -- WAVファイル(24ビット)のデータ
   type Wave_Data_24_Sample is mod 2 ** 24;
   type Wave_Data_24 is array (Positive range <>) of Wave_Data_24_Sample;
   for Wave_Data_24'Component_Size use 24;

modってのは余りを求める二項演算子じゃなかったっけ?
Unsigned_32もこんな定義みたいだけど。

これは、2 ** 24で割った余りの分しか格納しない整数型の宣言です。
modで宣言した場合は、普通の整数型とは違って、オーバーフローのチェックもなされません。
続いて、その型の配列を宣言してます。
ここでComponent_Sizeを設定しないと、アライメントされますので、結局32ビット使ってしまいます。

**って何だっけ?

……そこからか……べき乗演算子ですよ。BASICで書けば^。

そうすると、この配列に普通にアクセスすると、24ビット分だけ取り出してくれるわけか。これはいいな。
よーし、早速帰って続きを書くぞ!

Ada.Streams.Stream_IO

翌日。

おおい、酷いじゃないか。
Stream_IOでファイルを読み込むとStream_Elementって型になるけど、これをWave_Data_24_Sampleに代入しようとするとエラーになるぞ。
変な型を作ったせいじゃないか?
Sequential_IOってのも見つけたけど、ヘッダーを読み飛ばすからこれは使えないし。

おお、Ada.Streams.Stream_IOに行き当たったとは、偉い偉い。
でもストリームを使いこなせてはいないようですね。

人をなんだと思って……

試しにIntegerでやってみればいいじゃないですか。それもエラーになりますよ。

……あれ、本当だ……。
Stream_IOってなんのためにあるんだ、使えないライブラリじゃないか。

まあAdaには使えないライブラリが多いのは確かです。
でもこれは違いますね。
異なる整数型同士の代入には、型変換が必要ですよ。

とすると、Wave_Data_24_Sample型の変数 := Wave_Data_24_Sample (Stream_Element型の変数); って書けばいいのかな。
あ、通った。
ごめんごめん、でも1バイトずつこんなことをしないといけないなんて、Stream_IOはやっぱり使いにくいライブラリだよ。

型の方に'Readと'Writeって属性がありましてね。Stream_IOはそれを使うようになってます。
直接Stream_Elementを使うのは、それこそバイト単位で操作したい時ぐらいですね。

   File : Ada.Streams.Stream_IO.File_IO;
   A : Wave_Data_24_Sample;
begin
   ...
   Wave_Data_24_Sample'Read (Ada.Streams.Stream_IO.Stream (File), A);
   ...
end;

な、なんだってー!

強い型弱い型

……やっぱり納得できないな。
違う整数型同士だとキャストが必要なのは変わらないじゃないか。
'Readなんて、言語がStream_IOだけをピンポイントでサポートしてキャストを書かなくてもいいようにしてるだけじゃないか。

そうか、君はstrong-typedの言語を使ったことが無かったんですね。

馬鹿にするなよ、僕は一応C++も押さえてるぞ。0xに付いていく気力がないだけで。
JavaでArrayListを使うとオブジェクトなら何でも入って危ないけど、vector<string>なら文字列しか入らないから安全だ、ってあれだろう?
これは整数同士でも弾かれてるし、毎回キャストするなら間違った型でも通ってしまって逆に危ないんじゃないか。面倒なだけで。
strongとは言えないなあ。

Javaにジェネリクスが入ったのも最近のような随分前のような気がしますが、それは置いておきましょう。
あとAdaの型名を関数のように使う書き方の型変換は、いわゆる値キャストのみですので、そこまで危険でもないですが、これも置いておいて。
そうですね、C++でvector<string>を使うのはどういうときでしょうか。

そんなの一杯あるよ。vectorは便利だからね。
ええと、前にXMLを調べるツールを作った時も、ファイル名の一覧だろ、ファイルの中身を読み込むバッファだろ、タグのid一覧だろ、あと確か処理順をコントロールするためのスタックにも使ったな、これはstackにするべきだったかもだけど、結局STLのstackもvectorのラッパだし。

typedefしました?

勿論したよ。filename_listにid_listになんだっけ。後でlistやsetに変えるかもしれないしね。
でも結局vectorのままだったなあ。

すると、今まで直接代入できていたファイル名の一覧とidの一覧が、その変更によってcopyアルゴリズムを使わないといけなくなっていた可能性もありますよね。

おいおい、ファイル名とタグのidを混ぜるなんて、バグじゃないか。
そりゃたまたま同じ型だけど、流石にそんなことはしないよ。

でもそのバグはコンパイル時には検出できないわけですよ、C++ですと。

食い下がるなあ。
それは検出できると便利かもしれないけど、あれでしょ、Modulaのopaque typeや、O'Camlでシグネチャを書いたら本当は同じ型でもモジュールの外からは違う型に見せることができる、みたいなあれでしょ。
C++にはモジュールがないからできないのは仕方ないし、Adaにもprivateって予約語があるからできそうってのは知ってるよ。
でも今回のこれと関係ないじゃない。両方整数型ってわかってるんだから。

しばらく離れてたっていうわりには色々知ってますねえ。
まあいいや、strong-typedの言語では、型は宣言毎に全部別の型になります。
抽象型が別の型扱いされるのは機能上当然ですが、それよりも広い機能ですね。
Pascalでも、別々の場所で宣言した配列型は同じ中身でも代入できなかったでしょ。

でもC言語でも別々に宣言した構造体は同じ中身でも代入できないじゃない。
Pascalのはその配列版ってだけじゃないの?
やっぱりPascalでも違う範囲を持つ整数型同士は代入できたし。

Adaのはその整数版なんですよ。
逆を言えば、Modulaなんかは別々に宣言した構造体でも中身が同じなら代入できますよね。
宣言毎に区別される型と、中身で区別される型があって、どの型がどっちかは、言語によって違います。
で、前者をstrong-typedと言います……言うと思いますよ。

ちょっと待って、混乱してきた。
表にまとめてみよう。

言語整数配列構造体テンプレート抽象型
C/C++暗黙に変換元々代入できない宣言中身なし
Pascal暗黙に変換宣言宣言なしなし
Modula暗黙に変換中身中身知らない宣言
O'Camlユーザー定義不可中身宣言中身宣言
Ada宣言宣言宣言宣言宣言

ええと、知らない、ってのは私も知らないので置いておいて……。
テンプレートってC++以外はファンクタのことだったりgenericのことだったり……?
Pascal系のレコードを構造体と言っちゃうのも気になりますが、まあいいです。

Adaは見事に宣言が並んだね。
なんでもかんでも別扱いしちゃうと、逆に不便と思うんだけどなあ。

まあ、そういう言語なんです。
付け加えるとAdaにもPascalやModulaみたいな部分範囲型はちゃんと別にあって、そちらは元の型と互換性がありますよ。

Long_Integerに戻って

そういうわけで、Adaでは整数であっても用途別に新しい型を作るのがセオリーですね。その方が確実に欲しい範囲の型になるわけですし。
たまたま同じだからって、既存の型を再利用するのは勧められないです。
汎用の整数型というのも無いと困るので、そういう場合はIntegerやその部分範囲のNatural, Positiveを使いますが、Long_IntegerやShort_Integerを使うことはほとんどないような。

ちょっと待ってよ。
じゃあLong_Integer、Short_Integer、あとUnsigned_32みたいなのは使っちゃだめだってこと?

まあ、共通の固定サイズの型というのは、あるとやっぱり便利なんですよ。
Interfacesは名前の通り元々外部とやりとりする用途っぽいですが、範囲も決まってるわけですし、そのサイズの型が欲しい、後から変更する可能性もない、というときは入出力に限らず使っていいと思いますよ。
でもLong_IntegerとShort_Integerはだめです。

とにかくIntegerよりも大きい整数が欲しい時もあると思うんだけどなあ。

Longのほうはそうですねえ、でもそういうときは、中途半端にLongといわず最大のLong_Long_Integerを使うべきでしょう。Long_IntegerはIntegerと同じかもしれませんし。
Shortのほうはもっと用途がありません。
とにかくIntegerよりも小さな整数が欲しい、でも範囲を気にしない、なんてないです。

Wide_Characterの整数版でShort_Integerを使うことはあるんじゃない?
Cでもwchar_t代わりにshortを使うじゃない。

AdaではWide_Characterは16ビット固定、Wide_Wide_Characterが32ビット固定で、Short_Integerは環境依存ですよ……。
あとwchar_tは32ビットの環境も多いんです、というかWindows以外はほとんど32ビットです。shortが16ビットの保証もないわけですし、そんなコードは捨てましょう……。

ああ、そういえばサイズ不定のwchar_tは使えないということでchar16_tとchar32_tが追加されたんだったっけ。
結局使えるのは、サイズ固定、レジスタサイズ、最大サイズの3種類ってことかな。
でも、じゃあなんでShort_IntegerやLong_Integerなんてあるのさ。

C言語にshortやlongなんてあるからですよ……。

サイズ不定の型にはサイズ不定の型でないと対応が取れないというわけか。
やれやれ……結局AdaもC資産無しには使えない言語か……。