刺激反応系

檜山正幸 (HIYAMA Masayuki)
Tue Feb 15 2005:start
Tue Feb 15 2005:draft

刺激反応系という概念を紹介する。刺激反応系は、Chimairaの中核概念のひ とつである。システムやコンポネントは、刺激反応系としてモデル化される。

目次

1. はじめに

Chimairaにおけるコンポネント概念(Janusコンポネント)について、その概 要は既に説明した。(記事「Janus(ヤヌス)の紹介 -- stdin/stdoutからの入門」参照。)だが、今までの説明では、コンポネン トを考える上での外枠を与えただけで、実質を与えてない。これでは、設計や 実装の指針にはならない。

では、コンポネントの実質とは何だろうか、どうのように考えればいいのだ ろうか。ここで登場するのが「刺激反応系」という概念である。つまり、コン ポネントの構造や振る舞いをモデル化するときは、「コンポネントとは刺激反 応系である」と考えるのである。よって、刺激反応系は、Chimairaにとって中 核的な概念である。

この記事では、刺激反応系とは何であるかを伝えるため、インフォーマルに スケッチ(素描)する。

2. 刺激反応系と4つの動作

刺激反応系(stimulus-and-reaction systems)は、かなり一般的な概念で、 この世の中の「システム」と呼べるようなモノの多くは刺激反応系と考えるこ とができる。刺激反応系を考えるときは、対象となる系と共に、その系を外か ら見ている(認識している、関心を持っている)誰か/何かを考える。この 「認識主体 = 系を見ている誰か」は、通常は「わたし」である。以下では、 カタカナ書きのワタシは、系を見ている誰かの意味で使う。(「ワタシ」は檜 山の一人称ではない!)

刺激反応系の例としては、ソフトウェア・システム以外に、寒天培地で飼育 されている菌類とか、電気回路とか、バネとおもりから成る系とかを考えても よい。いずれの場合でも、系を見ているワタシ側の行為/動作として、観測変更がある。‘観測’(observation)とは、系の状態を表現する(と 期待できる)諸量を手に入れる行為である。‘変更’(mutation)とは、要する に、系にチョッカイを出すことである。餌を与えたり、薬物を投入したり、温 めたり、電位を与えたり、叩いたり、ひっぱったり、回転させたり、光を当てた り、その他もろもろ、系に影響を与えそうな行為である。「変更」という言葉 は、ソフトウェア・システム以外ではあまり適切ではない。一般的な文脈では、 「変更」よりは「刺激」(stimulus)と言うのが良いだろうが、我々の興味は 主にソフトウェア・システムだから、この記事では「変更」を採用した。

ところで、系を見ていて、ときにチョッカイを出すワタシは、系の側から見 るとどう見えるのだろうか。ワタシが行う観測とは、系に影響を与えない(情 報取得だけ)と思える行為であり、ワタシが行う変更(あるいは刺激)とは、 系に影響を与えると思える行為のことである。系から見ると、そのような行為 を行うワタシを、直接的に対象(個体)としては認識できない。つまり、系か ら見るとワタシは外部環境になる。「ワタシ = 外部環境全体」とは言い切れ ないにしても、ワタシは、系の外部環境の一部分として“溶け込んでいる” (個体性を失っている)のだ。(この点は重要である。いずれ詳しく議論する 予定。)

外部環境から受けた刺激に対して、系が(因果的に)行う動作が、‘反応’ (reaction)である。この反応には、遷移応答がある。‘遷移’ (transition)とは、系の内部状態が変わることである(*注1)。遷移は、外部から観 測できるとは限らない。むしろ、通常は観測できない。一方、‘応答’ (response)は、外部から認識できる“系からのメッセージ”である。応答も 一種の観測可能量であるが、スカラー(例:実数値)の組ではなくて、事象の 生起なので、いつでも観測できるスカラー的量とは区別して考える。

注1

言うまでもないことだろうが、遷移には「何も変わらない」というNo Operationの遷移も含まれる。動作や行為というときには、常にNo Operation も念頭におくべきだ。

ソフトウェア・システムだけに限れば、「観測、変更、遷移、応答」の典型 的解釈は次のようになる。

3. 4つの動作を解釈/分析する

前節で、刺激反応系に関わる4つの動作「観測、変更、遷移、応答」を挙げた。 ワタシの視点で見ると、遷移はやや異質である。観測と変更は、ワタシが主体 的/自発的に行うことができるが、遷移は“変更の影響”として系の内部で起 こる事象であり、ワタシからは直接的には見えない。見える(認識できる)可 能性があるのは、応答のほうである。その応答にしても、見る気/聞く気がな ければ逃してしまうだろう。

一方、系の側に視点を移すと、観測や変更を系が自発的に行うことはできな い。系は、観測や変更を受ける側である。あるいは、観測や変更を待つ 側である。変更(刺激)がやって来れば、それに応じた遷移と応答は、系が自 分の裁量で行える。どのように遷移しても、どのように応答してもそれは系の 勝手である。が、遷移/応答のタイミングを系が自発的に選べるわけではない 点には注意しよう。

4つの動作「観測、変更、遷移、応答」は並列的/対等に並んでいるわけでは なくて、それぞれに微妙な差があるのだ。ここで、少しまとめておこう。

観測と応答は、どちらもワタシが系を認識する手段だが、ソフトウェア的に は、その差を次のように述べられる。「観測とは、副作用を持たず値を返すメ ソッドの呼び出し」だとすれば、ワタシはいつでも観測を行える。観測のタイ ミングは自由だし、事前の準備なしで、思い立ったその場で遠慮なく観測を実 行できるのである。それに対して、「応答とは、内部状態の変化に伴って発生 するイベント」だとすれば、イベントを受け取る準備がなければ、応答をつか まえることができない。単なる観測に比べて準備手続きと注意を向けているコ ストがかかる。それに、イベントを受け取る側になるとは、“待つ立場”にな ることで、自発的に自由に観測を実行できるのとはワケが違う。

NOTE: Janusの極性と能動性/受動性

メソッド呼び出しやイベント生成に関して、自発的/自由にそれを“行える 立場”と、それを受け止めるために“待つ立場”があることがわかるだろう。 Janusでは、この立場の違いを極性で表現している。同じインターフェー スFooでも、それを自発的/自由に使えるものとみたときは+Fooとプラス符号 を付ける。それを受け止めて、サービスを提供する義務があると みたときは-Fooとマイナス符号を付ける。

記事「Janus(ヤヌス)の紹介 2 」第4節で、 符号をどう割り当てるべきか、さんざん悩んだことを書いたが、このとき割と 大きな決定要因になったのは、「能動的立場で見る→積極的に 見る→positiveに見る→プラス」という連想だった。

同一のインターフェース(規約)で、符号がプラスとマイナスのポートが揃っ てはじめて、そこに通信チャネル/相互作用経路が確立する。これはまた、サー ビスの利用者(顧客)と提供者(業者)のあいだで契約が成立し、サービスの 流通/運用が開始されることだ、ともいえる。

NOTE: 待つ立場

自発的に行動できる立場と待つ立場は随分と違う。

僕は山と川の里(つまりド田舎)で育った。小学校の同じクラス(1学年1クラスだったが)の 友達は、みんな川釣りをよくしていた。釣りをしないのは僕くらいだったろう。 魚がかかるのを待っているのがどうもイヤだった。ひざに漫画を置いて読みな がら釣りをしたこともあるが、こんな態度で釣果が上がるわけはない。

一方、“ヤス”というフォークのような形の道具で、魚を突くのはよくやっ ていた。鮎のような素早い魚を突くのは難しい。僕には無理だった。が、ウナ ギやナマズはそっと近づいて逃げる前に突くと、うまくつかまえることができ る。同じ時間やって、釣りとヤス突きのどっちが効率的かというと、たぶん釣 りのような気もするが、待つのが嫌いな僕はヤスしかやらなかった。

4. ワタシから見た系の分類

前節から、ワタシが自発的に行える動作は「観測」と「変更」であり、準備 をして関心を向け続けることによって受動的に行える動作が「応答の受理」で あることがわかった(応答の生成は系が行う)。

視点をワタシに置いたとき、「観測」、「変更」「応答の受理」のどれが行 えるかによって、おおざっぱに系を分類できる。まず、3つの動作からいくつ かを選び出す組み合わせを列挙しよう。

  1. {}
  2. {観測}
  3. {変更}
  4. {応答受理}
  5. {観測, 変更}
  6. {観測, 応答受理}
  7. {変更, 応答受理}
  8. {観測, 変更, 応答受理}

1番目は「観測できず、何の手出しもできない」系であるから、あまり意味が ない。その他はいずれも、日常的にお目にかかる例がいくらでもある。もっと も、分類基準を意識しないと、違いや特徴も目にとまらないかもしれない。こ のような粗い分類基準でも、知っていれば「ものの見方」が随分と変わると思 う。

さて、実例をあげる前に、1つ注意をしておく。それは例外(エクセプション) の解釈である。例外はプログラミング言語や実行環境が持つ機構/機能である。 今述べているのは、系(システム)に対する定式化である。よって、例外を系 の振る舞いの実現にどう利用するかに「これだ」と決まりがあるわけではない。 ある場合は、例外を観測(アクセッサ)の特殊な戻り値のように解釈していい かもしれない。また別なときは、瞬時に確実に戻ってくる応答とみなしてもよ いだろう(例外処理が応答受理)(*注2)

注2

「例外をメッセージングに使ってはいけない」といった規範があるようだ。 それはたぶん正しい習慣なのだと思うが、ほとんど常に僕は、そのテの「よい 習慣」の議論には関心がない。念のために言えば、「関心がない」とは、「反 対している」とか「意義を認めない」という事とはまったく違う。個人的に興 味がわかないから、話題にしたくないのである。

次の節で例を出すが、上に列挙した分類基準に従い、2番目のタイプの系は 「{観測}-系」、 5番目のタイプなら「{観測, 変更}-系」のような呼び方をす る。

5. さまざまな刺激反応系

まず、{観測}-系から考えよう。これは、「観測もできる系」という意味では なくて、「観測しかできない系」である。つまり、変更の手段は一切与えられ てないし、応答もないのである。このような系とは結局、基本データ型(の値) とみなしてよいイミュータブル・オブジェクトである(*注3)

注3

「{観測}-系とはイミュータブル・オブジェクトである」と結論するのは、実 は飛躍が含まれる。観測しかできない系が、外部からの刺激ではなくて、自発 的/自律的に内部状態を変えていく、ということがあるかもしれない。だが、 では「自発的/自律的」とは何かと考えてみると、それほど易しいことではな い。この問題は、因果性(causality)と密接に関連している。いずれ、自律 性、因果性、システム境界などの問題をまとめて論じることにしよう。

例えば、JavaのStringや(プリミティブ型intに対応する)Integerなどがそ の例となる。観測しかできないとは、アクセッサ(副作用がなく、値を返すメ ソッド)だけが備わった系ともいえる。基本データ型やアクセッサに関しては、 「正しいプログラムを書くには 〜 JUnitを超えて〜」フォローアップ・ページで随分と書いている。例えば、 次の3つのエッセイはいずれも、基本データ型とアクセッサに関するものである。

次は、{変更}-系。これは例えば、ログ・ストリームが例となる。 printとかwriteLogのようなメソッドがあって、文字列メッセージを書き出せ るとする。ひたすら書き出すだけなら、状態を照会する必要もないし、受け取 るべき応答もない。ログ・ストリームにエラーが起きたかどうか知りたいこと はあるだろうが、これがエラーメッセージを書き出すためのログ・ストリーム だとしたら、「ログ・ストリームにエラーが起きた」と書き出すべき先がどう せない。エラー発生を知ったからといってたいした対処はできない。だから、 void print(String message); というメソッドだけでも非現実的というわけで もない。メソッドprintはログデータの状態を変えるので「変更」である。

{応答受理}-系としては、キーボード・デバイスに対するソフトウェア的なモ デルが考えられる。人間の身体的行為としての打鍵を、キーボード・イベントに変換 して配布する。このコンポネントは、応答(イベント)の生成だけを行い、キー ボード入力を処理したいコンポネントが応答を受理(イベントをリッスン)す る。

{観測, 変更}-系の例はいくらでもある。例えば、スタックがそうである。

{観測, 応答受理}-系は、イベントの発生源であり、状態照会ができるものな ら、なんでも例になる。例えば、その先にネットワークやデバイスが繋がって いるイベント発生ポートで、オンラインかオフラインかを照会する(問い合わ せる)メソッドが備わっていれば、それは{観測, 応答受理}-系となる。

{変更, 応答受理}-系には、変更の影響(成功/失敗でもよい)を戻り値や例 外ではなく、イベントで通知するタイプの系が含まれる。変更メソッド(ミュー テータ)がリモートメソッドであったり、キューイングする非同期呼び出しだっ たりすれば、このタイプの系になるだろう。

{観測, 変更, 応答受理}-系は、次の節で例を出そう。

6. 有界スタックの例

僕がしょっちょう引き合いに出す簡単なデータ構造オブジェクト、それはス タックだ。で、またスタックの例を使う。インターフェースは次のようだとし よう。

interface Stack {
 double top();
 void pop();
 void push(double val);
}

このスタックは有界(bounded)だとしよう(*注4)。つまり、一定数 (例えば20)以上の項目を積めない(例えば、配列で実装されていると想定せよ)。

注4

この文は曖昧ですね。スタック仕様を満たす実装のひとつがたままた有 界であるのか、有界スタックの仕様を考えて、有界スタック仕様に対する実装 を考えるのか、さて、どっちでしょう。まー、どっちでもいいのだけどさ。

スタックが空のときと一杯のときは、例外ではなくてイベントで知らせると する(ちょっと、作為的だがね)。次が、通知イベントのインターフェースで ある。なお、スタックが空/一杯のときのtop, pop, pushの挙動は適当に決め ておく。

interface StackError {
 void stackEmpty();
 void stackFull();
}

さて、このような仕様のスタックに対する実装は、{観測, 変更, 応答受理}-系となる。アクセッサtopで観測ができ、ミューテータpop, pushで 変更ができる。stackEmptyとstackFullという応答を受理すれば、何が起こっ たかを知ることもできる(*注5)

注5

何が起こったかかを正確に知るには、“仕様”が要る。例えば、stackFullが 発生するタイミングは、最後となる項目(例:20番目の項目)が積まれたとき か、それとも、もう積めないのにpush要求が来たときか?

この仕様があまり便利でないのは、ワタシが能動的に呼び出すインターフェースだけ では、スタックが空/一杯のような事態を検出できない点だ(だから、作為的 なのだが)。しかし、変更のたびに、マジメにスタックからの応答を聞けば、 安全なスタック操作をチャント行える。

さてここで、通知インターフェースを次のように拡張してみよう。

interface StackNotification {
 void stackEmpty();
 void stackFull();
 void stackPoped();
 void stackPushed(double val);
}

このインターフェース(のポート)をマジメに聞けば、スタックに何が起き たかを完全に追跡できる。(「初期状態が空」のような決まりが必要だが。)

以上に述べた例はスタックという比較的簡単なデータ構造だが、ツリー構造を ベースにすれば、おおむねDOMの話になる。

7. そして、これから

{観測, 変更, 応答受理}-系は、一番一般的な系だが、そのぶん、解析が大 変になる。動作の一部分だけに注目した、{観測, 変更}-系 や{変更, 応答受理}-系を詳しく調べることにより、刺激反応系に対する知見 がいろいろと得られる。僕が、 「正しいプログラムを書くには」で特に強調したのは、{観測, 変更}-系に 対して、振る舞いの制約を(部分的でもいいから)記述してみることであった。

ここChimairaサイトの記事では、その他のタイプの系も調べて、最終的には、 一般的な刺激反応系の細かい解析を行いたい。Janus(ヤヌス)の枠組みと刺 激反応系の解析結果を組み合わせて、外枠と中身の両方を持った形でコンポネ ントの定義/定式化をしたいわけだ。