見出し画像

Retro-gaming and so on

ストリームは分かりづらい

プログラミング言語で使われるストリーム、と言うのが極めて分かりづらい。

ストリーム、と言う英単語自体は、例えばWeblioなんかでは次のように説明されている。


流れ、川、(特に)小川、(液体・気体などの)一定の流れ、流出、奔流、(時・思想などの)流れ、傾向、(能力別による)学級、分級

なるほど、分からん、である。

ある世代には城達也がDJを勤めていたラジオ番組が連想されたり、あるいは黒い三連星によるフォーメーションでの攻撃しか思いつかない。



そしてそれはサトリームである。

いずれにせよ、そもそもストリームと言う単語自体が日本語化してても抽象的でピンと来ない。
そして、これがプログラミング言語の話になると尚更サッパリである。

あくまでLisp、特にSchemeで言うと、以前チラッと書いたが、ストリームと言うのは無限長リストの事を指す。
ただし、一般的には入力に関する用語だ、と言って良いだろう。
ストリームの説明は次のページに書いてある通りだ。

ITの分野では連続したデータの流れや、データの送受信や処理を連続的に行うことなどを意味する。

ふーん、なるほど、と一応納得してみよう。
ただし。
何がややこしいと言うかと言えば、実際問題、このテの「ストリーム」と言うのは、プログラミング言語の内部の話をしてないのだ。どっちかと言うとハードウェアやらOSの話に属する。
だから考える事さえメンド臭くなるのだ。

まず入出力を考えるのがメンド臭い。
今は例えば入力の話に限定するが、A社のキーボードからの入力から来る信号と、B社のキーボードからの入力からの信号が同じである保証がない。A社はA社で独自の技術を使ってて、B社はB社で独自の技術を使ってる可能性がある。しかもそれらの信号をどう扱ってるのか。企業秘密になっちまえばどうなってんだかサッパリ分からんのである。
最悪のケースだと、A社とB社のキーボードの規格でさえ違う可能性があるのだ。例えばキー数が違う、とかな。
「うそ!」とか思う?実は我々はそういう時代を経て今に至るのだ。
アメリカ市場では、Windows 95が出るまで、複数のメーカーから複数の「全く互換性のないパソコン」が販売されていた。
  • Apple II
  • ATARI 400/800
  • Commodore 64
  • IBM-PC/AT
  • ATARI 520 ST
  • Commodore Amiga
日本でもそうだ。ザーッと挙げると
  • NEC PC-8801
  • NEC PC-9801
  • SHARP X1
  • SHARP X68000
  • Fujitsu FM7
  • Fujitsu FM-TOWNS
ここにはメジャーだったマシンを挙げているが実際問題まだまだあるだろう。キーボードにも互換性がないし、OSも基本バラバラである。つまり、A社のパソコンのOS上で動くプログラムを作って持っていってもB社のパソコンじゃ動かない可能性があるわけだな。当然である。何もかも違うのだから。そして入力で考えると各社のキーボードが同一規格だと言う保証さえない。そういう時代があったのだ。
これはパソコンだけの話ではなく、メインフレームとかミニコンでも同じだった。各会社(例えばIBMとかDECとか)によって売られているメインフレーム/ミニコンには互いに全く互換性が無い、と考えて間違いなく、A社の入力システムとB社の入力システムが違う、なんつーのはザラだった。と言う事は、仮にソフトウェアを移植するとしたら、まず皆がトラブるのは入力部分の書き換えがシャレにならんほど難しい、と言う辺りである。うひゃあ!
さて、これらのメインフレーム/ミニコンが中古になって保証が切れた時。OSの保証期間も終わってるのでさぁどうするか、と言う問題が出てきた。会社側としては新製品を買ってもらいたいトコなんだけど、購入者側としてはそうもいかない。決して安くないコンピュータを更に使い続ける為にはどうすれば良いのか。そういう状況で出てきた「ハードウェアに依存しないOS」が、ご存知UNIXである。元々中古資源を再利用するために作られた寄生虫のようなOSなのだ。
UNIXの目的はA社のコンピュータだろうがB社のコンピュータだろうが、「同じ動作をする」ようなOSになる事だった。そこで、入力のめんどくさい部分を取り敢えず「隠す」ようにすることとした。カッコよく言うと隠蔽化、だな。蓋を開けてみれば依然として各社によるややこしいハードウェアとのやり取りが存在するが、取り敢えずその辺は見てみぬふりをする事とした。
そして、入力にまつわる部分をあたかもファイルを扱うように見せかけた。こういうのを抽象化と呼ぶ。
つまり、それら抽象化により、メーカーによっての入力のハードウェアレベルやそこに極めて近いアセンブリレベルの細かいトコは隠蔽して、全てのコンピュータで同じようにプログラムすれば同じように動くようにしたわけだ。その辺で出てきたアイディアが標準入出力であり、またストリームである。特に標準入力は、当時の「会社別にキーボードの規格さえ統一されてない」状態で、最大公約数的に「最低このキーはあるだろ」と言う予想の元に決定されたモノだ。
余談だが、ISO(国際標準規格)で「標準キーボード配列」が制定されるのは1984年で、UNIXが登場する遥か後である。JIS規格は比較的ISOに準拠してるので世界的に見ると問題が少ない配列だが、一方、アメリカみたいに我関せず、でANSIでISOと互換性の無い制定をしている例もある。いわゆるASCIIキーボードは後者に属してる。日本でもASCIIキーボードのファンがそれなりにいるのは事実だが、どっちが「(政治的に見て)正しい」のか、と言うと、残念ながらJIS規格の方だ、としか言いようがない。ASCIIキーボードは世界的に見るとあくまでアメリカローカルの規格かつ異端児であって、JISの方がむしろ世界標準に、完全に同じとは言えないにしても、近い、のである。
いずれにせよ、C言語なんかで扱われる「標準入力」のキーの種類に対する前提は実はかなり古いアイディアで、それこそISOのキー配列もそろそろ考慮に入れても良いんじゃないか、とか思うんだが、その辺の改訂は全然行われていないようだ。
話をストリームに戻そう。そういう、「入力をファイルのように扱う」前提で、「データの流れのように抽象化する」と言う意図を込めて、ストリーム、と呼称されたわけだが。これにより個々のハードウェアの「違い」に左右されず入力にまつわるプログラムが書けて、また、プログラミング言語自体もあたかもハードウェアに依存してなく「独立した」存在のように見せかける事が出来る。
ところが厄介な事がある。プログラミングを学んでる際には、この辺の入力にまつわる動作が良く分からんのだ。何故なら我々は入力をしながらプログラミングをしてるわけだけど、その入力最中に入力を扱うようなプログラムを書いてると、動作確認が厄介だ、と言う事だ。結果、説明見てもそれこそ抽象的で、何だかピンと来ないんだよな。
プログラミングを学ぶ際に、プログラムを実際書きながら、動作を確認する事は極めて重要なんだけど、入力にまつわる辺りは非常にやりづらい。プログラミング言語が持ってる高級な関数だと理解は容易だが、低級な関数だと動作の確認がしづらいんでホントサッパリ分からん、と言う事が起こり得る。
いや、実際僕もよう分からんかったもん。最初に書いた通り、まず「ストリームって何やねん」って事から始まり、低レベル関数の動きなんざサッパリピンと来なかったのだ。確認が容易ではないから、だ。

peekとは何なのか、と言う質問が教えて!gooに上がっていたが、ちょっとここではANSI Common Lispを使って解説を試みようと思う。
ANSI Common Lispが便利なのは、設定した文字列を入力ストリームに結びつける、と言ったなかなかおかしな(笑)機能を持ってるから、だ。そうすれば仮想的な入力、を用いて比較的分かりやすい説明が可能だろう。
例えば次のようなコードをCommon Lispのインタプリタに入力する。

CL-USER> (defparameter sp (make-string-input-stream "abcde"))
SP


マクロdefparameterは(普通の言語で言う)大域変数を設定する。ここではspと言う変数を設定してるが、その値は(make-string-input-stream "abcde")と結び付けられている。
で、make-string-input-streamだが、こいつは引数にある文字列("abcde")をあたかも標準入力から入力されたように扱う関数である。それがキモで、要するに、今、キーボードからa、b、c、d、eと打ち込んでみたのと同じように扱う、ってワケだ。
次に関数readだが、これはフツーに無引数で使うと、端末から入力されたモノを受け取るけど、第一引数で入力ストリームを指定すると、そいつを読み込む。今、変数spを入力ストリームとして設定したので、readでそいつを読んでみよう。

CL-USER> (read sp)
ABCDE


spが持ってる"abcde"が読み込まれ表示されたのが分かるだろう。
ここまでは想像通りだと思う。問題は、だ。もう一回readを使って同じようにspを読み込んでみたらどうなるか、だ。


今度はエラーになるのだ。何故?入力ストリームには何も存在しないから、である。
ここで重要なストリームの性質と言うか原則がある。ストリームは消費対象である、と言う事だ。基本的にはストリームは一回使ってしまったら元には戻らない。いつまでもそこに残ってる、と言うような性質のモノではない、のだ。
これは考えてみれば当然で、パソコンを使ってる時、キーボードから我々はガンガン入力していく。その情報がいつまでもパソコン内(具体的にはメモリ上のどっか)に残っていたら、最終的にはメモリがパンクしてしまう。従って、入力による文字情報が処理されたら速やかに退場してもらわないと困るわけだ。
これが入力にまつわる基本動作、となる。
さて、Common Lispには、readより低レベルな動作を行うread-charと言う関数がある。これはC言語におけるgetcharに似てる。要するに入力から一文字一文字取り出す関数である。
もう一回spを定義しなおして、spread-charを適用してみよう。


"abcde"から一文字づつ取り出して表示し、6文字目になるとエラーを起こすのが分かるだろう。つまり6文字目では入力ストリームがすっからかんになる。
C言語だろうと内部動作的には似たような事が起こる。まぁ、Common Lispのように任意の文字列を直接入力ストリーム内に突っ込むような事が出来ないんで、この例のように確かめようはないが、想像は付くんじゃないだろうか。
で、peekである。peek-charはこのようにストリームを消費しない。文字情報はそのままストリームに残る、ってのがいわゆるpeekの特徴である。またspを再定義して調べてみよう。


peek-charは第一引数にtを取るが、それは「空白文字をスキップせよ」と言う意味だ。今は空白文字が無いのでその辺はどーでも良いが。
しかしread-charとの動作の違いを見てみよう。peek-charでは何度入力ストリームであるspを読み込んでも#\aと言う出力しか返さない。要するに、ストリームを消費してないから、だ。だからずーっと入力ストリームである文字列"abcde"の先頭を指したまま、だ。
ここで分かるのは、
  • read-char(C言語で言うgetchar)は入力ストリームの先頭を表示して消費する
  • peek-char(C++で言うpeek)は入力ストリームの先頭を表示しても消費しない
と言う事である。
peekと言うのは「覗く」と言う意味だが、だからこそ先読み、と言われるのだ。「次に来る入力」を先読みする。しかし、ストリームに含まれる「文字情報」を消費するわけではない、と言うのがread-char(C言語で言うgetchar)との大きな違いなのだ。

いずれにせよ、入力ストリームはパッと聞いても意味が分かりづらいのは変わらんと思う。Common Lispみたいなヘンな機能を持った言語じゃないと、「動き」が想像しづらいのは事実である。いや、Common Lispみたいなヘンな機能を持ってても、実際今入力してるわけではないので、やっぱ分かりづらい、って言えば分かりづらい機能だろう。
抽象化、ってのも良し悪しである一種の例なんじゃなかろうか。
  • Xでシェアする
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする

最近の「プログラミング」カテゴリーもっと見る

最近の記事
バックナンバー
人気記事