ホンマ、星田さんは勘がイイ。
そして人生で一番大事なのは勘じゃないか、と個人的には思っている。
ashはRacketに入ってるか謎だったので2で割ればええやんと思ったけど、このためにashを使ってたのか・・
うん。
この辺ちょっとだけ説明しよう。
これもコンピュータの「内部的」な話になる。
まずは概論から。
コンピュータの内部では全部が全部二進数で表現されてる、と言うのは良く知られたトコだろう。
例えば32ビットだと
00000000000000000000000000000001
てのが1。2は
00000000000000000000000000000010
となり、3は
00000000000000000000000000000011
となり・・・ってのは現代人は、具体的に二進数を弄る事がなくても「常識として」知ってるたぁ思う。
概要だけならな、って事だ。
ところで、CPU内部に於ける掛け算や割り算だが。
これは原則「シフト」と言う操作で行われている。
シフト、って言われれば難しい、と思うかもしんないけど、単純に上のようなバイナリの列をそのままのカタチで左や右に「ズラす」事を意味してる。
例えば1、
00000000000000000000000000000001
と言うバイナリを左にそのままズラすと
00000000000000000000000000000010
となる(長さは32ビットなので32でそれ以上は増えない)。
そうすると十進数では2になるのが分かるだろうか?
もう一回左にシフトすると、
00000000000000000000000000000100
これは十進数では4となる。
もう一回左にシフトすると
00000000000000000000000000001000
となり、十進数では8となる。
つまり、「左シフト」と言われる操作は、一回で2を掛ける作用に相当するわけだ。
逆に「右シフト」と言うのは一回行えば十進数的には1/2を掛ける作用に相当する(※1)。
原理的にはコンピュータでの乗算・除算は内部的にはこのようなプロセスをもって行っている(※2)。
(この辺は筆算で何個か試してみれば納得できる範疇である)
さて、ANSI Common Lispではashと言う関数で左シフトや右シフトが行えるようになってるが。
ashって字面を見ると英語に堪能な人は「灰?」とか思うかもしんない。
でもこれは「灰」ではなく、arithmetic-shiftの略だ。
当然Schemeにはarithmetic-shiftはビルトインされてはいない。
ここで星田さんにTips。
何かSchemeで機能が足りなさそう、とか言う場合は、まずはSRFIを漁ってみる。
そうすれば、ANSI Common Lispに影響を受けたライブラリが見つかる可能性がある。
まぁ、「可能性がある」だけで無いかもしれないし、また、あっても実装してるたぁ限らないけどね。
ただ、面倒を回避するならまずはSRFIを見てみるクセを付けよう。ビット操作に関しては、事実、SRFI-60ってのが提案されている。
そしてRacketでSRFIを使う場合、例えばこの場合だと
(require srfi/60)
と書けば目的のSRFIがロードされて使えるようになる。
とは言っても、実際問題、arithmetic-shiftはRacketにビルトインで搭載されてます。
言い換えるとSRFIで関数名にアタリを付け、RacketのDocumentationで探す、ってのが次のテです(※3)。
Racketではそのものズバリ、arithmetic-shiftと言う名前でビットシフトの機能が提供されています。
Racketの機能を駆使すると、Land of Lispのguess-my-numberと言うゲームは次のように書くことが出来ます。
#lang racket
(define *small* 1)
(define *big* 100)
(define (guess-my-number)
(arithmetic-shift (+ *small* *big*) -1))
(define (smaller)
(set! *big* (sub1 (guess-my-number)))
(guess-my-number))
(define (bigger)
(set! *small* (add1 (guess-my-number)))
(guess-my-number))
(define (start-over)
(set! *small* 1)
(set! *big* 100)
(guess-my-number))
※1: なお、これは別に二進数特有の現象ではない。
十進数で左シフトを考えると1 -> 10 -> 100....となっていって「元の数に10を掛けた結果になってる」ってのは明らかだろう。
すなわち、左シフトを一回行えば、何進数だろうと基数(2とか10とか8とか16)を掛けた結果が返ってくるのである。
右シフトも原理的には全く同じ(例えば10000->1000->100...)である。
※2: ちなみに、このため、小数点以下は原理的には扱いが難しくなる。
そこでCPUには「十進数モード」を付け足すのがかつては行われていたが、80年代後半辺りからはPCでは「浮動小数点コプロセッサ」を付け足すのが流行り、現在のCPUには「浮動小数点コプロセッサ」が初めから組み込まれていたりする。
また他の定番はSRFI-13(文字列ライブラリ)とSRFI-14(文字セットライブラリ)で、この3つは良くお世話になるし、これら無しで素のSchemeでプログラムを書くと格段にメンド臭くなること請け合いだ。
なお、これらに含まれる関数はRacketビルトインで提供されているものもあるので、Racketのドキュメンテーションと比較しながらどれがビルトインでどれがそうじゃないか、順次把握していくのが良いだろう。
逆に、SRFIで提供されてるけど、Racketの組み込みの方がより強力だ、と思われるモノに構造体なんかがある。
SchemeはPascal(と言うよりAlgolか?)に名を負うモノが多いが(例えば構造体はPascalではレコード型と呼び、Schemeではrecord-typeと呼称するのを好む)、一方、RacketはANSI Common Lispに従うような名付けをするのを好んでいるようで、Racketの構造体はstructと言い、Schemeのrecord-typeと違い、単一継承も可能なスグレモノとなっている。