UNIXという考え方
という名の書籍が出てます。オーム社、Mike Gancarz、訳by芳尾 桂
ISBN:4-27-406406-9
あれ?ISBNうまく出ないなあ。使い方間違ってるでしょうか?
この本のもう一つの楽しみかた:unixという考え方のどこがどう
駄目なのか?を考えるための読本として読む(笑)
GNOME作者の『unix終わってる!(りょうせいさんとこに和訳あり?)』
との併読がお奨め(笑)
unix人がどこをどう勘違い(今から思えば)したのか?が
結構読み解ける感じです。
例えば、(programを)keep smallしましょうといっても、
実は1次元ではなく、どこをどう小さくすると良いか
見極めるって課題が有る。
unix人はそこを(今から思えば)読み違えてる。
p22。
半日で寿命が尽きるまでに一目散に生殖するという
単機能(笑)かつ短寿命な虫
(イナゴの大群の中の個体とかのことらしい)
をunixプロセスに喩えているが、
まさに読み違えを浮き彫りにするという意味でも良い喩えだ。
これは、unixプロセスは、簡単な構造だが長寿命なモノを
サポートするのが不得手だ、と暴露しているようなものだ。
言い換えれば、「機能」とは「時間?」を持たないものだ、と
決め付けているということだ。
runではなくexistを第一義としたoopのほうが、
長寿命を簡単に記述できる(そして一方で、短寿命の記述を
阻害している訳ではない)という意味では、
unixの流儀よりも更に優れたkeep smallの実現方法
なんじゃないか?と、俺が思う所以。 -戯
あの文脈でunixのプロセスを虫に喩えるのは、実は間違っていると思う。
虫がしている唯一の行為である生殖は、子孫を残すための行為だ。
虫の集団は、個体の生殖を通して、そういう大集団の寿命を、維持しているわけだ。
一方、unixのプロセスは、子孫を残さない。パイプで繋げば
複数プロセスが合体することは有るが、短寿命のモノを
幾つ並べても短寿命のままなのであって(笑)、それは生殖とは違う。
あるいは入出力をファイルに落とすことをもって寿命の延長に喩える
という手も有るが、ここで問題になるのはファイルのデータ構造の体系が
unixには存在しない事だ。プロセスの構造は存在するのにね。
思うにnixのプロセスが残すファイルは、生命たる子孫なんかではなく、
せいぜい(それよりはフォーマットにおいて数段下の存在である)
タンパク質の肉団子に過ぎない、のではないだろうか?
つまり虫個体から見ればそれは餌か仲間の死体かのどっちかだ。
寿命だって、それ自体は充分シンプルな概念であるはずだ。
しかしunixはそこから背を向けた。
寿命を簡単に記述する手段を手放した。
卑近な手段として寿命を記述するためにしばしば(無限)ループが使われる。
そして、それを使った瞬間に、unixといえどもWindowsを笑えなくなるのだ(笑)
thttpd (Tiki:thttpd)の起動コマンド thttpd_wrapper は、thttpdが落ちるのが前提で書いてある。いさぎよし。長寿命なものを作るのはコストがかかるから短寿命ですませる部分は済ませる。これはこれで見識。runかexistかは、UNIXがどうのこうのとはあまり関係ない気がするが。。カーネルだけ長寿命、あとはぼちぼちでいいんじゃないだろうか。。httpdとかデータベースサーバーとかORBとか最近は落ちちゃ困るサービスが増えてるから一概にはいえなくなってきてますが。
- 落ちちゃ困るソフトについて。GUIプログラムなんかはどうですか?
いやGUIに限らず、もっと古典的な例としてEmacsやviなどのエディタがあります。
最近に限らないと思います。DBサーバーだって結構伝統ある存在ですし。
- p53。よりによってEmacsを『unixアプリの悪い例』と呼んでいますね(笑)。
どういう意味で悪いのかが判りにくい一文ですが、もし虫の話の延長として
長寿命ソフトを悪く呼んでいるのだとすると、エディタがすべからく悪いソフトに
分類されてしまうOSなんてものが、そもそも変だと感じるほうがマトモでわ -戯
- ToolBoxアプローチではなくキッチンシンクアプローチであることを批判しているだけでは?
- コストはそんなに違うんでしょうか?Objectはメモリに”有る”だけで
その役割を果たすわけです。尤も”有る”を”実行する”でエミュレート(笑)しようと
すると、それは確かに少なくないコストとなってしまうのでしょうけど。
つまり、最初から不得手なように作ってあるというだけなんじゃないでしょうか?
- 例えば短寿命な構造でエディタを作ろうとしたら却ってコストも嵩む変な構造に
陥ってしまうのではないでしょうか?1文字変更するたびにfileを吐き出して終了
するような”エディタ”を与えられたら(勿論sedはここでは考えません)、
誰でも泣くと思いますが…。
- 結局、長寿命に向いた問題領域と短寿命に向いた問題領域がある、というだけのことだと
思います。それを今までは短寿命ベースの仕掛けでなんでもやろうとしていただけで。
- で、その”今までの”設計方針がunixには内在する、という主張が、
この本にはある(と少なくとも俺には読めた)、ということです。 -戯
典型的なUNIXアプリにはコマンドパーサーが無い。
その代わり、コマンドの起動時に、ユーザーがコマンドラインに
動作パラメータも入力することが前提となっている。
プログラムの肥大化は、それでは防げないのではないか?
コマンド起動時引数と起動後コマンド入力との間に
フクザツ度の差が有るのか?どちらも文字(列)じゃないか?
更にいえば、GUIのCheckBoxプチプチ押すほうが更にシンプル
(つまりよりUNIX的)だ、ということになりかねない(笑)。
この本は、シンプルという概念の”原因”および”結果”を、
(今から思えば)勘違いしてるのだと思う。
原因とは、ナニをすればシンプルになるか?という判断であり、
結果とは、シンプルにすれば何がもたらされるか?という判断
(この本では大抵”UNIX的”になるとまとめられてしまっている)である。
まぁ意図的にそうしている可能性も有りそうな気がするけど。
結局unixのアプローチは、(常にではないがしばしば不可避の)
対話型Interfaceを、複雑さから救うことが出来ない。
皮肉(だと指摘されているが)でも何でもなく、
プログラムが人と対話しないとならない状況は、
必ずしばしば有るのだ。if文の後ろのカッコを
常に計算機が自律的に解決できるわけではあるまい。
要するに部品化のこと(メリット)を色々な角度から
説明(説得)している感じ。
だが他の部品化方法は無かったのか?
他の部品化の方法と比べた場合、どっちがどう
優れているんだ?…という話は、少なくとも
前半には(笑)書いていない…
結局unixのpipeアーキテクチャの問題は、
”pipeでviエディタを作れるか?”の一言で
要約されるだろうと思う。
そういう方向性の部品化だって有るハズなのに、
unix pipeはそうではない。-戯
--
テキストエディットコントロールというコンポーネントを書くことはできても、
テキストエディットコントロールをコンポーネント組み立て方式で書けるか
と言えば書けないですよね。
- viをパイプで書けない問題とそれほど違わない = エディタは例が悪い
スラドでも指摘されていたはずですが。
- 書けないわけじゃないですよ。もっと細分化すればいいのだから。
- カーソルコンポとか行コンポとか。作りたければ幾らでもどうぞ。
- まあ、そういうコンポーネント同士の依存関係は少々強くなっちゃいますが、それはまた別の問題。
ここでの問題は状態マシン(?)の問題なので。
部品化を『層』で表現しているようだが、
他は無いのか?少なくともpipeはNetworkモデルを
うまく扱えないようで、つまりpipeは階層化モデルをしか
サポートしていないように見える。
『木を守る』とのことだが、
Printerを使わないという意味でなら賛同するが、
Treeモデルのみの世界に拘泥するという意味なら
結構心配だ(笑)
層の話と少し関係があるが、unixのpipeは結局1次元だ。
つまり、入口と出口の二つの口しかない。
せっかく計算機のランダムアクセスアーキテクチャが
思考を次元の束縛から開放してくれたというのに、だ(笑)。
それに、CPU稼動率を競ってもしょーがなく、
それよりは常時”触れる”計算機が真にパワフルな計算機だ、と
p48に書いてあるじゃないか。ならば、
触れるが殆ど常時独り言しか言っていない計算機ソフト/OSに、
パワフルの称号は与えられないんじゃないのか?
逆にいえば、UI以外はバックグラウンドに回してしまえば目的は果たせるはずで、
”UI以外をバックグラウンドに回す”ことがスムーズに記述できないことを嘆くほうが
本末転倒じゃないのではないかと…
よく言われる(この本でも言及してる/というか言及を避けている(笑))問題である
UIの問題は、たぶん二次的問題なのだろうな。
元々はやはり”状態を保持することをサポート(=簡単にする)してくれない”ことが
問題なんだろう。UIの問題は”命令ではなく状態に基づくUI”をサポート
してくれない問題、と言い換えられそう。
状態に基づくUIとは、例えばGUIのボタンだ。
それを押してもボタンは相変わらずそこに”ある”だろう
(勿論消すことも出来るが、それは権利であって義務じゃない)。
命令によるUIならば、さっきと”同じ(正確には同値の)”選択肢が
再び現れたとき、その選択肢はさっきと”同じ(同一の)”選択肢
なのか?などと悩むことはできない。できないというのは不可能
という意味であって、不要と言い換えることが安全に可能であるかどうかは
ひたすら案件に依存する。無理がある場合も有るはずだということだ。
状態によるUIならば、”同一の”選択肢を示すのは容易だ。
同じボタンを相変わらずそこに置いたままにしておけばよい。
GNOME作者氏のunix批判(?)は、直接的には、OOPでいうところのClass
(しかも柔らか系で実行時にinterfaceを列挙できるような奴)の
ようなもののunix上での不在を嘆くモノだ。
だが、半分はそういう問題だが、残り半分は二次的問題なんだろうな。
やはり”状態”を持つアプリの作成をサポートする部品化手法
の不在を嘆いている(のから話が派生してClassの話になる)のだろうな…
と憶測しますぅ。 -戯
よしんばFilter softという考え方が優れているのだとしても、
unixのfilterアーキテクチャがどれくらい理想から離れたサブセットなのか?
という問題は残っている。
IOが1つづつしかないのは健全といえるのか?
Error処理のサポートはあれでよいのか?(この本いわくソレはStderrに
レポートするものなのだそうだ。するとそれを監視する人間様が必要なのか?(笑)
いや勿論stderrをどこか別のところにリダイレクトすりゃヨイんだけど、
それじゃ結局話は元に戻るだけだし)
- stderrをstdoutと別のファイルにリダイレクトするのはなにか問題ありますか?
“元に戻る”がどこにもどるのかよく分からないです。
- 出力が1つから2つに増えたのならば、Lispみたいなやり方(?)を使えば
世界を色々広げる余地が有るかも知れませんね。ただ、その用途にstderrを
消費してしまうと、今度はエラー出力が何処に行けばいいんだろう?、
という意味で、元に戻るなぁと思いました。
p101
入力をstdinから取得するプログラムは、
どこからデータが来ても構わないと仮定している。
”どこから”の意味を(場合によっては)履き違えているといえそうだな。
unix pipeは、
アプリケーションインスタンス(プロセス)1つづつに別々の接続先を
指定することは出来るが、
1つのプロセスで複数の接続先を”同時に”使うことが出来ないじゃないか。
いや、情報を区別する手段が有るならどの手段でもよいのだけど、
複数のプロセスの出力を混ぜて次のプロセスの(1つの)stdinに
突っ込むことは出来る(shスクリプトで()を使って書くアレね)が、
それはあくまで混ぜているだけであって、
受け取ったプログラムの中でどうやって
混ぜたものを分離するか?の処方箋を、
unixはナニも与えてくれない。混ぜ損である。
その破綻ぶりがすごく卑近に判りやすい例の一つが、diffだと思う。
入力が二つあるというただそれだけの理由で、入力ストリームを
いったんファイルに落とさないとならない(笑)。
それとも敗北を認めて(笑)名前付きpipeとかを使うとか?
- pipeを使う単体の道具としての個々のプログラムは入力がひとつしかないですが、
組み合わせる糊としてのshは、複数の入力をかなり自由に切替えできますよね。
- (俺の望みでは)切り替えちゃ駄目なんです。複数のターゲットに常時(?)接続とか。
- また、あるProcessの出力「を?」あちこち切り替えることも出来て欲しい。
今与えられている仕掛だと、そのProcessを生成する時に一度だけ接続先を指定できるだけですよね。
- MidiPipe の説明をちょっとだけ読みました。まだ充分理解してませんが、
複数の入力を複数のコンポーネントにつないで、いろいろな音をつくり出す仕組みは、
人間がマウスでつんつんするんですよね。
- また勝手にチョキチョキしました。失礼。 -戯
- つんつんしても良いですし、外部MIDIを受信させても良いし、
自Process内でテキトウな(Timerオブジェクトとかを使って)
トリガーを出しても良いと思います。
- コンポーネント自身が、自分の接続先を指定したり切替えたり操作はできませんよね。
- いえ。たまたま現存するClassにはそういうのは無いですが、作ろうと思えば
無理無く作れるはずです。
- 自分でも出来ますし、「他人にやらせる?」ことも出来ます。
他のコンポのアドレスを知ることさえ出来れば(そしてそれは難しくない)、
ドウにでもなります。
- UNIXのパイプのシステムでも、つないだり組み合わせたりする機能は個々のツールには
必要無くて、それらを統合する sh が担当します。sh にはそういう機能が充分備わってますよ。
コマンドラインからひねりだすワンライナーではなく、ある程度大きなシェルスクリプトに
なってしまいますけど。
- あるProcessInstanceを生かしたままで、それの入出力先を外部からの命令に応じて
切り替えるってことは、余程凝った(=非標準な)仕掛をしないとunixでは無理かと。
個々のProcessが独自の"状態"を持っていないならば何回殺して作り直しても
"同じ"なのですが、不幸にしてUnixのProcessは状態を持っている個体です。
MidiPipeなら、状態つまり個体を維持したまま接続を切り替えることは、
なんら支障なくやれます。
- Process(でもObjectでも何でも良いんですが)が"状態"を持つことと、
それを組み合わせてバッチ処理に供するコトが有るってこととは、
相容れないことなんかでは無いのは周知の事実と思われます。
自分の状態に応じて刻々処理を変えていくのがProgram(少なくとも
UnixのProcess)の普通の姿ですよね。
- 状態が無い(Stateless)ならば、構わないのですけどね。
(それを 参照透過性? ってゆーの?)
プロセスごとにオープンしたパイプのファイル記述子をdupで切り替えていけば複数の子プロセスとパイプのやりとりは簡単にできますよ。
- パイプ"を"やりとりしても、あまり嬉しくないのでは…
- パイプしか切り替えられないなら結局はLispやOOPから見てサブセットに過ぎないんで。
- 子プロセスという概念自体も鬱陶しいし。
- "子プロセスと"しかできないんですか?(子とはshellの子という意味でしょうか?)
だったら使いにくいです。親も子も無くMessageをSendしたいですから。
- 簡単と呼べるかどうかが疑問です。どういうラッパーを書けば"簡単"になるんですか?
- "どの"ファイル記述子を支配できるんですか?
既知の奴(stdin/out/errの3つ??)だけじゃ弱すぎます。
頭から尻尾まで連続して処理行おうぜっていう
手続き指向(ってのかな)のモデルの拡張の余地は、
unix程度のソレが限界なんじゃないかな?
- オブジェクト指向対手続き指向なんじゃなくて、対話処理対一括処理なんじゃないですか?
パイプ処理は一括処理のモデルだからエディタを作れなくて当然だと思うのですが。
- PlugWare の作者氏が言っていた「unixのpipeは枝別れしない?」って指摘は結構鋭いと思います。
一括処理であっても枝別れは出来て欲しいはずなんだけどなあ。
MidiPipe なんかは或る意味で対話処理にもバッチ処理にも適用できるモデル(の実装)だと思います。
- OOがどう関係してくるか?というと、コマンドを起動してから死ぬまでに「他者が?」それに介入する手段が
原則的に無い、という(手続き型の)問題をどうにかしたいなぁ、ということなんです。
べつに「他者?」はユーザー(UI)でなくても良いはずです。他のObject(?)でも良い。
- 状態モデルが実行モデルに唯一の実体としてハードコードされてて他から手出しする術が無い、
というモデルがウザイんです。喩えるならCのstaticなローカル変数みたいなものでして、
実行文脈に束縛された形でしか状態が持てないってのが。
解決策(?)を妄想:
- ProcessIDをshell変数や環境変数に入れる(というやりかたで
shellでプロセスを捌こうという発想)。
で、ProcessID「へ」ナニかを行うというような文脈で
プログラムを書く(というのが出来るようなシェルを作る(笑))
- そのProcessの子ProcessのIDを変数に入れることが出来ないぞ。
ProcessIDが直接は取得できないから。つまりScope(笑)が
親Processの内側に隠れてしまっているわけだ。
- ps使って子供をアドホック検索するか?ださい(遅い)ねえ。
- ProcessIDを取得したはよいが、それでナニをする?
つまりID「へ」メッセージをどう投げる?
- Process口は入口出口1つづつは流石に駄目だが、LispのCellみたいに
1つの入口と2つの出口という風にしたらどうか?
- いずれにせよ、Processを「作った後に?」、接続先の変更を
「外から命じて?」行うことが出来ないと話にならない。
- やっぱりProcessの能動性オンリーが壁だな…。
なんかどうやっても駄目なような気がする。
unixと、(例えば)Smalltalkを比べると、その違いの"急所"は何処だろう?
- unixとSmalltalkを比べて、(無理やり)対応するところを挙げてみると…こんな感じか?
(あくまでこれは一案。
プロセスとファイルのどちらをobjectに見立てるかで、
流派が大きく分かれそうだ。)
- kernel <=> VM
- memory空間 <=> object空間
- プロセス <=> object
- ソース <=> クラス(のメソッドの記述)
- ファイル <=> (イメージ中に永続化した)object
- コンパイル <=> コンパイル
- ちょっと考えたのだが、もしかして急所(の1つ)は"main()"の有無じゃないか?
- mainとは、1つのunixのプロセスのLifetime全体をカバーする関数。
- mainとは、プロセス内の他の全ての関数を(直接または間接に)呼び出す関数。
(存在するけど使ってない関数は無視するとして…)
- Smalltalk(OOP)等には、そんな"カバーする関数"や"全てを呼ぶ関数"は無い。
- コンストラクタは、作ったらすぐ終わる。
- デストラクタだって、消されるときに始めて呼ばれるだけ。
- 他のどのメソッドだって、用が済んだら直ぐ終わる。
- 他の全てを呼ぶなんていう無茶苦茶なメソッドは、
(普通は、というか一般的には)無い。
- どのメソッドを呼ぶかは、
main以外の他の(しばしばobject外の)メソッドが
勝手に決める。
- 一方unixは、プロセスの外からプロセス内の特定の関数を呼ぶことは、
(基本的には)出来ない。
どれを呼ぶかを外から決める事も出来ない。
- あとはobject間の関連。
- unixだと、プロセスがプロセスを"指す"という関係は、
(親子プロセス以外は)あまり使わない。
- そのunixの親子関係も、自由度は低い。ex:勝手に養子縁組できない。
- pipeで繋ぐ対象を、プログラム名じゃなく、プロセス(PID)にすればいいのでは?
(最終更新 Wed Jun 15 14:49:36 2005)