星田さんの記事に対するコメント。
SchemeにあるってとはRacketには無いのか。
いや、あります。
心配せんでエエです(笑)。
DD&Dの関数を作っている途中で要素の型を調べたくなった。Gaucheだとtypeof?だったかな?そういうので調べられたと思うんだけど、Racketには特別なライブラリを入れたりしないと調べることは出来ない?いやまさかそんな・・
うん、こりゃ難問なんだよな。
例えば、ANSI Common Lispなら確かにtype-ofってのがある。
ANSI Common Lispなら「型」をキチンと返してくれるが、Schemeにはそれがない。
Schemeの仕様書には次のような事くらいしか書かれてないんだ。
これだけ、だ。
一体どういう事だろうか。
元々、原理的に言うと、コンピュータに於いて型ってのは存在しない。
敢えて言うと2進数で表される整数しか存在していない。
つまり、この2進数で表される整数が、そのまま数値を表わすのか、あるいは文字を表わすのか、ってのは便宜上だ、って事になる。
例えば、ある局面に於いて、「今書いてるプログラム上は、この2進数の羅列を文字列と解釈したい」ってのはあくまで人間の、あるいはプログラマ側の都合なんだよな。
こういう「都合」が頻繁に生じる以上、人間が毎回毎回「これをアレと解釈してくれ」と言うのがメンド臭いんで、プログラミング言語側が「型」ってのを勝手に決めてくれる仕組みを持ってくれれば嬉しいわけだ。
そういう意味で「便宜上」型ってのが出て来たわけだな。それにより、「違った型同士の」演算を許す、とか許さない、って話が出てくる。
先にも「便宜上」と書いた通り、実はこれにも明快なルールがあるのか、っつーと実は「言語によって違ったりする」。
Racketでの例。型「文字」と型「数値」は足せない。
Pythonでの例。これも型「文字(あるいは文字列)」と「数値」は足せない。
C言語での例。C言語の場合、型「文字」と型「数値」を足しても構わない。SchemeやPythonだとエラーになるような「演算」でもC言語だと問題なく実行出来る。
上の3つの例を見ると、いわゆる先の2つはモダンな「高級」言語だと割に「当たり前」な結果なんだけど、C言語だと(モダンな環境に慣れた人の)直感に反して「文字」と「数値」を足せてしまう。
これは、C言語にとっては、本当にダイレクトに「扱ってるのは全部メモリに書きこまれた"数値だ"」って前提になってるからだ。本当にCでは「型」は便宜的にあるものに過ぎず、これがCが「高級アセンブラ」って呼ばれる理由の一つでもあるんだ。
これは別にC言語に関する「文句」ではない。単にC言語の設計者が「もうちょっとマシに書けるシンタックスを持ったアセンブリ言語を作りたい」と言う設計方針を持ってたから、に他ならない(※1)。
つまり、「型」は何度も書くが、「便宜上存在するモノ」であって、それがどういった性質を持つのか、ってのは言語設計者次第、っつーか好みの問題だ、って事。
実際、例えば往年、C言語のライバルと目されたPascalでは上記のC言語のような演算は許されない。
PascalでCと同じロジックで書こうとすると、コンパイラに怒られる。7行目で型「文字」と型「整数」を足そうとするのは許されない、と言うわけだ。
かつては、PascalとCは商売上のライバルっぽく扱われてたが、PascalはCのように「マシなアセンブリ」を目指して作られたわけじゃなく、ALGOLに始まった「正しい高級言語でのプログラムの書き方」を広める為に開発された。
要はCはアセンブリやマシン語に立脚してるけど、Pascalはそれこそ「生粋の高級言語として生まれた」わけで、この2つの言語は全く方向性が違うんで、実際同じフィールドで比較は出来ないわけだよな(※2)。
いずれにせよ、「型の扱い」ってのは「言語設計者の好み」がまずあるし、実際、既に星田さんは「整数と言う型と浮動小数点数と言う型は足せない」OCamlを見てると思う。
とまぁ、まずは「型と言うのは恣意的なモノだ」ってのが実は前提だ。
じゃあ、「型を言語設計者が自由に決めてる」として、だ。
ユーザーが勝手に型を作れる「ユーザー定義型」を導入するとして、じゃあ、言語が「どんな型でも完全に判定可能になるのか」って問題が出てくる。
要は、「型をユーザーが作れる」のなら、「どんな型が作られるのか」ってのは原則、言語設計者の予想の範疇外、って事になる。
例えばC言語の場合、極論「フツーの高級言語で言う型」を持たない。Cで言う「型」とは何ビット分のメモリを確保しろ、と言う指令、ってのがその実体で、Cで用意出来てない「型」に関しては、ユーザーがメモリアロケーションを使ってメモリ領域を確保するしかないわけだ。
また、「指令」である以上、「型の実体」が無いんで、結果型判定述語的なモノは存在しない。ある意味非常に割り切った設計になっている。
一方、ANSI Common Lispなんかでは「型」のヒエラルキー、要するに型の木構造を用意してて、ユーザーが定義した「新しい型」でもそのヒエラルキーのどっかに「必ず」属すような設計になっている。
ルートがTと言う型で、そこから「全部が派生」するようになってるんだな。
これはなかなか上手い手だし、このお陰でSchemeなんかにはない、型を直接定義可能なdeftypeなんつーマクロさえ存在する。
ANSI Common Lispのこの「データ型の木構造」を見ると何か連想出来そうでしょ?そう、オブジェクト指向だ。
つまり、ルートであるTの「性質」をその子は全部「継承」している・・・・・・。子の節も親の節の性質を「継承」している・・・。
オブジェクト指向って言い換えると、フツーのプログラマには役に立つかどうか分からんけど(笑)、言語設計者とか言語実装者にはかなりメリットがある考え方なのね。実際作るのに「オブジェクト指向の」言語を使うかどうかはさておき(※3)。
ANSI Common Lispの由来自体が元々「オブジェクト指向研究」の実験言語だった事もあって、こういう「オブジェクト指向的な」考え方が導入されている。
そして「ANSI Common Lisp」と言う仕様が要求する以上、実装側は「必ず」作ってるANSI Common Lisp実装にはこの「型の木構造」を内包せねばならないわけ。
そしてそのお陰でtype-ofって関数が存在出来るんだよ。ANSI Common Lispが持ってる「型の木構造」を探索して、一致する型の「名前」を返すようになっている。
じゃあSchemeはどうなのか、っつーと仕様上、この「型の木構造」なんつーのは持ってない。と言うか定義してないんだな。
ここでSchemeの哲学的なモノを考えてみる。ANSI Common Lispが用いた「型」の設計方法は唯一の方法論じゃない。他にもAlternative(選択肢)があるんだ。
そう、Schemeの場合、Alternativeがある場合、それを言語仕様に取り込もうとはまずしねぇんだよ。「必要最小限な最大公約数的な仕様」にする事だけは決めるけど、「実装に影響を与えそうな詳細には立ち入らない」ってのがSchemeのやり方だ(※4)。
良い言い方をすると、Scheme実装者には実装の自由度がある、って意味になる。
例えばGaucheなんかは確か、川合史朗さんがANSI Common Lispハッカーな事もあって、ANSI Common Lispばりのデータ型のヒエラルキーを持っていて、結果GaucheはSchemeの仕様で要求されていない「完全なオブジェクト指向言語」になっている。
結論から言うと、Racketはその成立(Scheme時代)から言うと、別に「完全なオブジェクト指向言語」を目指してなかった、って事だ。
結果として、Racketでtype-ofみたいな関数を使いたい場合、完璧ではないんだけど、describeと言うライブラリを導入するのが一番簡易だろう、って事になる。
端末から
raco pkg install describe
と言うコマンドを走らせる。
そうすれば、
とまぁまぁ(スペック的にはANSI Common Lispに届かないとは言え)型名をシンボルで返してくれます。
※1: C言語の前身のB言語には事実、アセンブリ言語と同様に「型が存在しなかった」。
しかし使ってみると、型が無いとプログラミングがしづらい、って事が発覚し、「型アリのB言語」としてC言語が作成された経緯がある。
※2: この2つが比較されたのは、パソコンで扱える高級言語の数が限られていた、と言うPoorな環境が招いただけで、どっちが「速い」かっつーのは実は関係がない(むしろ、当時の貧弱なPC環境だと「コンパイル速度」で言うとPascalの方が速く作業が終わった)。
何度か書いてるが、この2つは全く思想が違うし、決着はどっち付かずだったが、MicrosoftがWindows 95前後でC言語(正確にはC++)処理系をリリースしてPascalをリリースしなかった事により決着が付いた。
※3: Rubyなんかも「完全なオブジェクト指向」なのは、実装自体はC言語で行われてる模様だが、このデータ型の木構造(ヒエラルキー)をキチンと持っているから、だ。
言わば「オブジェクト指向言語」を名乗っていても、こういうヒエラルキーが存在しない言語もあって、そういう場合は「完全なオブジェクト指向」とは言えなかったりする。
※4: sortの話を覚えているだろうか?ソーティングアルゴリズムは色々あるわけだけど、これもAlternativeが色々ある以上、Schemeとしての仕様書には「それを含めろ」とは要求しない。結果、sortはSchemeの仕様書からは排されている。
sortがあれば便利なのは誰でも分かるが、Schemeの仕様は、かなり神経質に「どれを仕様に含めるべきか」考えている。