正しいプログラムを書くには 〜JUnitを超えて〜
増補版テキスト

檜山正幸
After JavaWorld DAY 2004
LastUpdate:2004-06-29

目次

NOTE: 増補版テキストとは

「JavaWorld DAY 2004」(2004年6月22日)で配布されたテキストに対して:

  1. 気づいた誤字脱字/間違いを修正し、
  2. 当日使用したスライドへの参照を適宜埋め込み、
  3. 多くの補足説明を「NOTE」という形で追加した
ものです。

当日の講演(セッションJA-2)に参加されなかった方でも、これをていねい に読めば内容をご理解いただけると思います。なお、講演者・檜山の悔恨と反 省の記はこちら

/* まだ未完成です。*/

NOTE: サブタイトルの由来

「JUnitを超えて」というサブタイトルは、『JavaWorld』編集長が集客に気をつ かって付け足してくれたものです(さすがに見出し/ヘッドラインのプロ)。 「JUnit」という単語にひかれて来てしまった方もおられたでしょうから、効 果はあったでしょうね。でも、内容的にはJUnitを超えた話なんてなくて、 JUnitを超えてない/JUnitの手前の話です。どうもスンマセン。

しかしですね、JUnitと今回の話題が一切の接点がないかというと、そうでは ありません。eXtreme Programming、テスト駆動開発、そしてJUnitなどが最近 人気を博しているという背景があったからこそ、私はこんな話題を選んだ(む しろ「選べた」)のです。JUnitを使う現場が増えているってことは、私から 見れば、形式的手法(今回の主題です)が受け入れられる素地ができつつあるっ てことですから。

まえおき

まず、…

→→スライド0:たんなる扉です

1. どんなことについて考えるのか

→→スライド1:上とまったく同じ文言です
→→スライド2:Bertland Mayerの本から「正しさ」について引用
NOTE: Bertland Mayerと『オブジェクト指向入門』

スライド2は、最後の 参考文献にも挙げておいた『オブジェク ト指向入門』からの引用です。著者のMayer氏はEiffel言語の設計者ですが、Design by Contractでも有名です。この『オブジェクト指向入門』は、何度読んでも/ どこを開いても、なにかしら示唆を得られる、ほんとに名著ですね。

NOTE: robustnessとcorrectness

「仕様で定義されてない状態で機能する」のがrobustnessだとすると、「状 態」も、その状態における「機能」も未定義です。もし定義するなら、それは 「仕様で定義されてない状態」じゃなくなるので、correctnessのほうになり ます。というわけで、robustnessは本質的に定義不能な概念なのです。

そういう事情で、ここではcorrectnessだけを扱います。正しい(correctだ) というのは、効率がいいとか、操作性が快適とか、安い/早いとかとは関係あ りません。それらはまた、まったく別な話題です。

→→スライド3:「正しさ」を定義するために必要な条件を述べています
→→スライド4:正しさを保証する方法について
NOTE: 証明とテスト

プログラムの証明については、たとえば『プログラム検証論』( 参考文献参照)の最初に、ユークリッドのアルゴリズム(互除法)が確か にGCD(最大公約数)を求めることを証明しています。実務的なプログラムに 対してこういった証明を付けることは、絶望的に困難(あるいはバカげたこと) です。

「形式的手法=プログラムの証明」と考えてはいけません。そう考えたら何 もできないでしょう。まずは、仕様記述の精度を高めることだ、と考えてくだ さい。仕様を厳密化すると、部分的に証明ができるケースを発見することもあ ります。そのときは証明もしましょう(別に証明を禁止しているわけでもない のです)。

スライド4で言っていることは、杓子定規に考えると「正しさ」の追 求は不可能(あるいは絶望的)になってしまうよ、ってことです。

→→スライド5:健全な妥協をしよう
NOTE: 潔癖にならない!

杓子定規に考えると「正しさ」の追求は不可能なのだから、杓子定規に考え ちゃいかん!てことになります。どんな手法でも、夢のような効果もなければ、 そんなに確実性もないのです。それでも、何らかの効果、多少のご利益があれ ば、それでいいじゃないですか。かけた労力にみあうだけ(あるいはそれ以上) の効果があれば満足できるはずです。

目次へ

2. わたしは何をいいたいのか

→→スライド6:上の箇条書きを簡略化して、少しやさしめに書き直しました
NOTE: 私の提案

ここまでで、今回の私の提案(あるいは主張)は出尽くしてます。ここから 先は、より具体的、詳細な説明です。

→→スライド7:ところで、ここでアンケート調査を
NOTE: アンケート

スライド7のinterfaceに対して「実装を3分以内で書ける人?」と聞いたとこ ろ、数人の方が手を挙げました。「実装を3時間以内で書ける人?」に対して は、多くの人の手が挙がりました。この結果は、私の予想どおりです。

→→スライド8:私が書いた実装です
NOTE: コンパイラは文句を言わない

下のinterfaceに対して:

interface DoubleStack {
 double getTop() throws Exception;
 void push(double value);
 void pop() throws Exception;
}
こんな(↓)実装でも:
class DoubleStackImpl implements DoubleStack {
 public double getTop() {
  return 0.0;
 }
 public void push(double value){
  ;// do nothing
 }
 public void pop() {
  ;// do nothing
 }
}
コンパイラは文句を言いません。

文句を言うとしたら、コンパイラではなく人です。でも、その人は何を根拠 に文句をいうのでしょうか?少し“つきつめて”考えてみてください。

→→スライド9: ダメな理由を考えてみよう
目次へ

3. 注意と約束ごと

→→スライド10: 注意と約束ごと、考え方/態度
NOTE: 正誤表

この文書では、 正誤表の修正を既に行っ ています。

サンプルプログラムは、擬似コードに近いもので:

NOTE: 擬似コードに近い

要するに、サンプルプログラムのようなコードをお手本にしちゃいけません よ(むしろ、反面教師)ってことです。だいたいの人はそんなことは見れば分 かるとは思いますが、老婆心で。

LIST1: アノテーションの定義(一部しか使っていない)
import java.lang.annotation.*;

/**
 * interfaceが仕様記述であることを示す。
 */
@Target(ElementType.TYPE)
@interface spec {
 // marker
}

/**
 * interface, classがサンプルであることを示す。
 */
@Target(ElementType.TYPE)
@interface sample {
 /** コメント文字列 */
 String value();
}

/**
 * 値がnullにならない(なってはいけない)ことを示す。
 */
@Target({ElementType.METHOD,
         ElementType.FIELD,
         ElementType.PARAMETER,
         ElementType.LOCAL_VARIABLE}) 
@interface NotNull {
 // marker
}

/**
 * 値がnullであってもよいことを示す。
 */
@Target({ElementType.METHOD,
         ElementType.FIELD,
         ElementType.PARAMETER,
         ElementType.LOCAL_VARIABLE}) 
@interface Nullable {
 // marker
}
NOTE: アノテーション

Java1.5のアノテーションを使ってみたことに本質的意味は何もありません。 物珍しかったので使ってみただけです。まー、アノテーションを契約 (contract)記述に使えないか?といった下心はあったにはあったんですが、ア ノテーションだけでは難しいようです。

目次へ

4. クラスとオブジェクトについて

「クラス/オブジェクトとは(本質的に)何であるか/何であるべきか?」 といった議論や定義は行わない。(理由:難しい、多様、別にどうでもいい、 不毛、宗教戦争だし、…… )

次のような、通常とはやや異質の定義(?)を使う。

注1

形式的な理論では、このインターフェースをなぜか指標(signature)と呼ぶ。 Javaとは用語法がまるっきり違うので要注意。

注2

アメナブル(後述)なインターフェースに対するホーア論理式だけを考える。

NOTE: クラスやオブジェクトの定義

上のクラスやオブジェクトの定義は理解しにくいと感じたり、違和感をおぼ える人も多いでしょう。ですが、私はこれはかなりいい定義だと信じています。 その理由のひとつは幾何学的/力学的な比喩(メタファー)が適用できるから です。実をいえば比喩ではなく、ホントにそうなのです。つまり、クラスやオ ブジェクトを幾何学的/力学的な表現/記述法で語れるのは、クラスやオブジェ クトが実際に幾何学的/力学的な存在だからです。

このような観点(クラス/オブジェクトを幾何学的/力学的実在とみなす) には、かなり有利/有効な面があります。幾何学も(古典)力学も、長い伝統 を持ち、きわめて良く整備された体系ができあがってます。こんなにも完成度が高 い体系を利用できることは、それだけで大きなメリットでしょう。それと、我々 は、かなり高度な幾何学的/力学的な直感を備えています。この直感を使える のもまたメリットです。

→→スライド11:世間でよく使われる“方法論”
NOTE: 精神論の効用

気力や根性を一概に否定はできません。「気合いが入ってないからバグが出 るんだ」ってのも、事実だったりもします。でももちろん、全てが精神論で片 づくわけはありません。根性もいいけど、根性だけじゃダメだよねぇ。

→→スライド12:他の手法って、どんな手法
NOTE: 形式的な手法 (formal method)

形式的手法(形式的技法)は古くからあります。 たとえば私は、1980年に出た『演習 プログ ラムの証明』(近代科学社)という翻訳本で、流れ図に関する証明を練習しま した。しかし形式的手法は、一貫して現場からは嫌われ続けた、いやむしろ、 嫌われさえせずに無視され続けた手法です。

スライドに書いた(「フォーマルな格好」「形式的な挨拶」の)ような意味で、 フォーマル/形式的が解釈されたことも、ホントにあったような気がします。 形式的手法には、現場感覚とズレた机上の空論というような匂いがしたの も事実です。

それでも私は長年、形式的手法のファンでした(専門家ではありません)。 その私が、eXtreme ProgrammingのTest-Firstというプラクティスを知ったと き、「これは、お化粧(変装か?)した形式的手法だ」と思いました。テスト コードは、かなりのところ仕様を記述しています。その記述は、自然言語ではなく プログラミング 言語によりますから、形式的(機械可読)です。若干の飛躍を許してもらえば、 「テストコード=形式的仕様記述」といえます。その“テスト=仕様”を最初に書 け!と主張しているのですから、こりゃ「形式的手法のススメ」以外の何もので もありません。

そういうわけで、XPやJUnitから、「上手に化粧(変装)をすれば、形式的手 法が現場に受け入れられる可能性がある」と教えられた(元気付けられた)の です。

→→スライド13:先に要点をまとめれば
→→スライド14:アクセッサ/ミューテータの別名
NOTE: アクセッサとミューテータ

これだけ別名があるということは、重要な概念であることを強く暗示しています。 クエリ/コマンド、オブザーバ/アクション、アトリビュート/オペレータ、 プロパティ/メソッド(狭義)という組み合わせがよく用いられるようです。

ただし、アトリビュート、プロパティの読み出し側(getter)はアクセッサで すが、書き込み側(setter)はミューテータになるので注意してください。

→→スライド15:さー、これから
NOTE: アンタねー

「さー、これから」ってアンタ(アンタ=私)、この時点でだいたいの人は もうだいぶ疲れてましたがね。「休息は大事です」なんて真実を述べている点 がまだしも救いでしょうか。

この増補版テキストも「さー、これから」と言いたいところですが、実際は、 ここらへんでやめてしまっても、まーいいんではないでしょうか。元気がある 方は次にどうぞ。

目次へ

この点からはじめよう

→→スライド16:この指示にしたがってください

「点」って、幾何学的な点です。

5. さまざまな点(Point)達

LIST2: 点その1
@sample("点その1:2次元の点")
class Point_1 {
 private double x, y;

 double getX() {
  return x;
 }
 double getY() {
  return y;
 }
 // ...
}
LIST3: 点その2
@sample("点その2:3次元の点")
class Point_2 {
 private double x, y, z;

 double getX() {
  return x;
 }
 double getY() {
  return y;
 }
 double getZ() {
   return z;
 }
 // ...
}
LIST4: 点その3
import java.awt.Color;

@sample("点その3:2次元色付きの点")
class Point_3 {
 private double x, y;
 private Color color;

 double getX() {
  return x;
 }
 double getY() {
  return y;
 }
 Color getColor() {
  return color;
 }
 // ...
}
LIST5: 点その4
@sample("点その4:2次元の格子点 (long版)")
class Point_4 {
 private long x, y;

 long getX() {
  return x;
 }
 long getY() {
  return y;
 }
 // ...
}
LIST6: 点その5
@sample("点その5:2次元の格子点 (int版)")
class Point_5 {
 private int x, y;

 int getX() {
  return x;
 }
 int getY() {
  return y;
 }
 // ...
}
LIST7: 点その6
@sample("点その6:上半平面にある点")
class Point_6 {
 /* y >= 0 でなくてはならない */
 private double x, y;

 double getX() {
  return x;
 }
 double getY() {
  return y;
 }
 /* 以下、 y >= 0 を仮定したコード */
 // ...
}

問:これらのクラスに、どんな継承関係がある(あるべき)と思いますか?

目次へ

6. 継承ではなくて、埋め込みと包含

「猫は哺乳類の一種(class Cat extends Mammal)」や「ジープは自動車の 一種(class Jeep extends Automobile)」は概念的に自然かもしれないが、 「3次元の点は2次元の点の一種(class Point3D extends Point2D)」はどう も変。(「2次元の点は3次元の点の一種」でもオカシイ。)「2次元格子点は2 次元の点の一種」と思えるが、次のソースはコンパイルできない(*注3)

LIST8: コンパイルできない
/* コンパイルできない */

class Point2D {
 private double x, y;

 double getX() {
   return x;
 }
 double getY() {
   return y;
 }
 // ..
}

class LatticePoint2D extends Point2D {
 private long x, y;

 long getX() {
   return x;
 }
 long getY() {
   return y;
 }
 // ..
}
注3

LaticePoint2DのgetX, getYの戻り値型をdoubleにすればコンパイルできる。

NOTE: ヒッカケ問題

前節の最後の問題は、ある種のヒッカケ問題です。「どんな継承関係がある (あるべき)と思いますか?」なんて聞いていますが、答えは、継承関係なん て別にどうでもいいだろう、というものです。もちろん、ピッタリした継承が 定義できるときもあるのですが、いつでもピッタリとはいかないのです。

現実世界や抽象的な機構を、なんでも「クラスと継承」という枠組みから解 釈したりモデル化する人がいますが、それは過剰使用(overkilling)というも のです。ヒンシュクを買うのを承知で言えば、継承なんてコード共有の一手段に過ぎ ません。

「クラス」を分類(または分類されたもの)、「継承」を分類上の階層関係 という意味で用いるのだとしたら、それはアリストテレスの時代から言われて いることです。なにも「オブジェクト指向」を引き合いに出す必要などなく、 普通の(伝統的)知的態度を取っているだけのことでしょう。

しかし、2次元点を3次元点に見なす方法、2次元格子点を2次元点と見なす方 法はある。

  1. (x, y) |→ (x, y, 0) として、2次元点を3次元点と見なせる。
  2. (x, y) |→ (x, y) (特に何もしない)として、2次元格子点を2次元点と見なせる(*注4)
注4

プログラミング言語の場合は、整数と実数(浮動小数点数)がまったく異な る種類のデータとみなされることがあるので、「整数2 → 実数2.0」のような変 換が必要になるケースがある。だが、この変換によって数学的な意味での値が 変化するわけではない。

(1)を‘埋め込み(embedding)’、(2)を‘包含(inclusion)’と呼ぶ。なお、

これを‘射影(projection)’と呼ぶ。埋め込みと射影は対になって登場するの で、その対は‘埋め込み/射影ペア(embedding/projection pair)’と呼ばれ る。
→→スライド17:埋め込みの図
→→スライド18:射影の図
→→スライド19:包含の図

・ わかったこと

クラス継承とは別に、対象物(ここでは点)の空間のあいだに、埋め込み (射影を伴う)や包含が存在することがある。埋め込みや包含の存在が、オブ ジェクトやクラスの類似性を与えているようだ。

NOTE: 背後にあるもの:計算現象

この節の記述はわかりにくいかもしれません(説明がよくないのですけど)。 別な方向から説明しましょう。

中島玲二先生の『数理情報学入門』(1982, 朝倉書店;残念ながら現在では入 手困難、実は私の手元にもないのです)に、次のようなことが書いてあります。

計算機やソフトウェアは人工の産物だが、そこに起るcomputingの諸現象には 自然現象に比肩する深さの構造が潜んでいるらしい。

私は、この意見にまったく同感です。「物理現象は実在する」というと きの「実在する」と同じ意味において「計算現象は実在する」のです。プログ ラマは、(部分的には)計算現象の世界に生きているわけですから、その現象 に関する理解や洞察力が必要でしょう。

先ほど私が、「幾何学や力学の概念が使えるのは比喩ではない」と言ったの は、計算現象が幾何学や力学として(幾何学や力学の定式化が使える形で)実 際に起きている、と考えているからです。

やれ“オブジェクト指向”だ、やれ“関数型”だという「パラダイム」の議 論は、現象というより、その現象を見る人間の立場(もっとハッキリ言ってし まえば、バイアスのかかり具合)の議論です。もちろん、我々はいかに(いか なる立場で)現象を見るべきかという問題意識はあっていいのですが、それが 政治・宗教論争になってしまっては不毛でしょう。

政治・宗教に感心がない私としては、立場(定式化の流儀)を超えた現象の 本質に興味があります。とりあえずここまでの文脈では、クラスの類似性(関 係性)の背後に、実体的集合(実はクラスの状態空間)のあいだの写像が存在 しているようだ、ということが言いたいのです。

NOTE: 「埋め込み、包含、射影」とクラス定義

埋め込みと包含の違いは(後でまた繰り返し触れますが)、埋め込みは偶発的な写像、 包含は必然的な写像ということです。 (x, y)→(x, y, 0)は確かに標準的な2D→3D埋め込みですが、 (x, y)→(x, y, 1)だって埋め込みです。(x, y)→(x, y, 0)しか埋め込みがな い、というわけではないのです。それに対して、格子点を実数2D空間に入れ る“自然な”方法は1つしかありません。

射影も必然性(一意性)を持っています。(x, y, z)から(x, y)を抜き出すや り方は一通りしかありません。 射影pと、pに対応する埋め込みeを1つ選んだペア (pとeは、p(e(x)) = x を満たす)は非常に扱いやすいものですが、いつでも うまくペアが作れるわけではありません。埋め込み/射影ペアの例としては、 「(x, y)→(x, y, 0)と、(x, y, z)→(x, y)」が典型的です。色付き2D点の 「(x, y)→(x, y, BLACK)と、(x, y, c)→(x, y)」も同様な例ですね。

別な例をあげましょう。人間の全体を表すつもりのクラスPersonを考えます。 Personには、名前、年齢、性別、既婚未婚などのフィールドが備わっていると します。Personのサブクラスとして会社員のクラスCompanyEmployeeを考えま す。CompanyEmployeeは、所属する会社、その会社における社員番号、役職 などのフィールドを持つものとします。また、Personのなかで、成人未婚男性 を表すクラスとしてBachelorを考えます。

CompanyEmployeeは、Javaの継承(extends)を使ってPersonから定義できそう です。では、class Bachelor extends Personはどうでしょうか? なんだかピ ンと来ませんね。この違いの背景は何でしょうか。

実は、CompanyEmployee → Personの向きに射影が存在します。 CompanyEmployeeインスタンスが保持する、所属する会社、社員番号、役職などを忘れて (無視して)しまえばPersonと見なせます。これは、3D点のz座標を忘れてし まえば2D点と見なせるのと同じです。一方、Bachelor → Personは包含です。 成人未婚男性だからといって、Personに対して(所属会社のような)何か特別 なフィールドを追加するわけではありません。Bachelorインスタンスは、何も せずにまったく自然にPersonインスタンスとみなせるのです。

背後に(つまり現象として)B→A という射影があれば、class B extends A の定義がうまくいきます。B→Aという包含のときは、クラス制約(constraint) が付け加わるので、extendsとは(キーワードextendsの語義はともかくとしても)相性がよ くありません。B→Aという包含を背景とするサブクラスBを「条件による (conditionalな)サブクラス」と呼ぶ人もいます。ある程度の設計・実装の 経験がある方は、“機能を付け足す感じ”のサブクラスと“制約で絞り込んだ 感じ”のサブクラスがあることは理解しているのではないでしょうか。

CompanyEmployee → Personの射影は自然に(必然的に)定義できますが、 Person → CompanyEmployeeという埋め込みはうまく定義できません。つまり、 すべての人を会社員とみなす方法が(自然でなくてもいい、としてもなお) 見つからないのです。 2D→3D((x, y)→(x, y, 0))のときとは状況が違うでしょう。このこと は、Person → CompanyEmployeeという型強制(別な型とみなす操作)を安全に定義できないというこ とです。 実行時のダウンキャストなら、例外を覚悟で可能ではありますが ((CompanyEmployee)aPersonというキャストは、aPersonが専業主婦や学生の ときは失敗する)。

目次へ

7. コード再利用の方法

→→スライド20:この指示にしたがってください
NOTE: コード再利用をささえる現象/状況

私は、人間の(集団によって共有される)理解の様式、解釈の立場、あるい は主義主張や文化・趣味を離れて、計算現象それ自体を問題にしたいと述べました。 「単一継承 vs 多重継承」、「継承 vs 委譲」などを趣味の問題だと片付けて は、おそらく袋だたきにあうでしょうが、このての議論を棚上げにして、たま にはその背後の現象や構造を見つめてみてはどうでしょうか。

以下の例で、背後にある「埋め込み/射影ペア」または「包含」の存在を確 認してください。そのとき、埋め込み、射影、包含の向きにも注意してくださ い。スーパークラス/サブクラスの関係、クラアント(機能の利用者)/プロ バイダ(機能の提供者)の関係、一般/特殊の関係などの“向き”と、写像の 向きはどう相関してますか? 矢印や図を描きながら吟味してみてください。

・ 継承

LIST9: 色なし点から色付き点
import java.awt.Color;

class Point {
 private double x, y;

 Point() {
  x = 0.0;
  y = 0.0;
 }

 double getX() {
  return x;
 }
 double getY() {
  return y;
 }
 // ...
}

@sample("色なし点から色付き点")
class ColoredPoint extends Point {
 Color color;
 
 ColoredPoint() {
  super();
  color = Color.BLACK;
 }

 Color getColor() {
  return color;
 }
 // ..
}

問:背後に埋め込み/射影ペアが存在します。どんな埋め込み/射影でしょう。

・ 委譲

LIST10: 3次元から2次元
class Point3D {
 private double x, y, z;

 Point3D() {
  x = 0.0;
  y = 0.0;
  z = 0.0;
 }

 double getX() {
  return x;
 }
 double getY() {
  return y;
 }
 double getZ() {
  return z;
 }
 // ...
}

@sample("3次元から2次元")
class Point2D {
 private Point3D point3d;

 Point2D() {
  point3d = new Point3D();
 } 
 double getX() {
  return point3d.getX();
 }
 double getY() {
  return point3d.getY();
 }
 // ...
}

問:背後に埋め込み/射影ペアが存在します。どんな埋め込み/射影でしょう。

・ 継承(制約チェックを伴う)

LIST11: 全平面から半平面
class Point2D {
 private double x, y;

 Point2D() {
  x = 0.0;
  y = 0.0;
 }
 double getX() {
   return x;
 }
 double getY() {
   return y;
 }
 void shiftX(double dx) {
  x += dx;
 }
 void shiftY(double dy) {
  y += dy;
 }
 // ..
}

@sample("全平面から半平面")
class UpperPoint2D extends Point2D {

 private Point2D point2d;

 UpperPoint2D() {
   super();
 }

 // getX() 定義不要
 // getY() 定義不要
 // shiftX(double dx) 定義不要
  
 void shiftY(double dy) {
  if (getY() + dy < 0 ) {
    // 下側に行ってしまう
    super.shiftY(-getY()); // y = 0
  } else {
    super.shiftY(dy);
  }
 }
 // ..
}

問:背後に包含が存在します。どんな包含でしょう。

・ 委譲(インターフェース適合を伴う)

LIST12: 全平面から格子
class Point2D {
 private double x, y;

 Point2D() {
  x = 0.0;
  y = 0.0;
 }
 double getX() {
   return x;
 }
 double getY() {
   return y;
 }

 void shiftX(double dx) {
  x += dx;
 }
 void shiftY(double dy) {
  y += dy;
 }
 // ..
}

@sample("全平面から格子")
class LatticePoint2D {

 private Point2D point2d;

 LatticePoint2D() {
   point2d = new Point2D();
 }

 long getX() {
   return (long)point2d.getX();
 }
 long getY() {
   return (long)point2d.getY();
 }

 void shiftX(long dx) {
  point2d.shiftX(dx);
 }
 void shiftY(long dy) {
  point2d.shiftY(dy);
 }
 // ..
}

問:背後に包含が存在します。どんな包含でしょう。

問:上半平面の格子点を定義してください。

・ わかったこと

再利用にはいろいろな形態がある。

  1. 埋め込み:元 → 新規 :拡大して再利用
  2. 埋め込み:新規 → 元:一部分を再利用
  3. 包含 : 新規 → 元:一部分を再利用

これらの再利用は継承や委譲、場合によりどちらを使っても行える。 ただし、どちらが自然かはケースバイケース。背後には埋め込みや包含が存在する。

NOTE: 「現象それ自体」ってなに?

前のノートで「現象それ自体を問題にする」なんて書いてますが、はたして そんなことが可能なのでしょう? 人は、理解の様式、解釈の立場から離れて、 なにものかを純粋に見て、なにごとかを客観的に語るなんて可能なのでしょう か? それはたぶん不可能でしょう。「計算現象が存在する」という基本的主 張も私のドグマだと言われてしまえば、それまでです。仮に計算現象の存在を 認めたとしても、その現象それ自体を問題にしたがる態度は、私の好みにあっ ているだけのことかもしれませんしね。

いま言えることは、(理由と根拠はなんであれ)私が「プログラミングの流 儀」や「開発の心構え」などには(今回の文脈では)注意を払ってないという ことです。したがって、「正しさ」は、実社会やプログラミング社会(?)の道 徳や正義とは何の関係もありません。ここでの“正しさ=correctnes”は、あ る対象に関する記述と、その対象の実際の振る舞いとのあいだの合致のことで す。そして、そのような記述や振る舞いを論じるにあたって、計算現象を前提 とする態度が(少なくとは私にとっては)健康的であり有効性を持つと期待で きるのです。

NOTE: 「埋め込み、包含、射影」についてもう少し

「埋め込み、包含、射影」の定義を少し厳密にしておきましょう。「埋め込 み、包含、射影」について、(曖昧でも直感的にでも)わかった気分がしてい る方は、この長いノートをスキップしてもかまいません。また、読んでみて、かえっ てわからなくなったとしても、気にする必要はありません。

・ 単射と全射

まず、集合論的な意味での写像(mapping)の復習です。A, Bが集合だとして、 写像 f:A→B が‘単射’(injection, injective mapping)だとは、a≠bならば f(a)≠f(b)のことです。要するに、A内の違った点はB内の違った点にマップさ れる、ということです。単射のことを埋め込みと呼ぶこともあります (が、ここではそう呼びません)。

写像 f:A→B が‘全射’(surjection, surjective mapping)だとは、 どんなb∈Bに対しても、f(x) = bとなるxをA内で見つけられることです。要するに、 fの値がB全域に渡っていることです。f(x) = b となるxは1つとは限りません。

写像fが全射の場合に限らず、一般的に、f(x) = b となるxを全部寄せ集めた 集合を考えます。それをf-1(b)と書くことが多いのですが、ここでは f*(b)と書くことにします。 記号的表現では、f*(b) = {x∈A | f(x) = b} ですね。

fが単射かつ全射だと、f*(b)はどうなりますか? 「空集合か単元 (singleton)集合」かつ「空集合ではない」のだから… そうですね、 f*(b)は、bがなんであっても単元集合です。fが単射かつ全射だと、集合Aと 集合Bはfを通じて1:1に対応することになります。

・ タプルと直積

a∈Aとb∈Bを組にした形(a, b)を‘aとbのタプル’と呼びます。平面の座標 (x, y)はタプルの(おそらく、もっともおなじみの)例です。さて、a∈Aとb ∈Bに対するタプル(a, b)をすべて考えます。こうしてできあがったタプル全 体からなる集合をA×Bと書き、‘AとBの直積’と呼びます。A×B×Cなども同 様です -- A×B×Cは、タプル(a, b, c)の集まりですね。ここで、掛け算の記 号「×」を使っているのは、それなりの理由があり、確かに掛け算と似たとこ ろがあるからです。(たとえば、A, Bが有限集合で、それぞれn個, m個のメン バーを持つとき、A×Bのメンバー数は?)

タプル(a, b)のaのことを、‘第1成分’‘第1の項目’‘第1座標’など と呼びます。bはもちろん、‘第2成分’‘第2の項目’‘第2座標’です。 たくさんの成分(項目、座標)を持つタプルに対しても、第n成分(項目、座 標)が定義できます。

タプル(a, b)の第1成分を取り出す写像 (a, b)→a は、集合A×Bから集合Aへ の全射ですね(全射はすぐ上で定義してます)。このように直積(タプルの集 合)から成分を取り出すタイプの全射を‘射影’と呼びます。

たとえば、3次元の点の座標タプル(x, y, z)から第1成分xを取り出すのは射 影です。それだけでなく、(x, y, z)から(x, y)を取り出すのも射影と呼びま す。つまり、直積から成分のいくつかを取り出すような写像が射影です。

・ 埋め込み/射影ペア

3次元の点の座標タプル(x, y, z)から第1-2成分を取り出す 射影(x, y, z)→(x, y)を考えます。z座標値を適当に選びます(z=1としましょ う)。この選んだ(固定した)z座標を使って(x, y)→(x, y, 1)とするのが埋 め込みの例です。埋め込みをe、射影をpとすると:

という重要な性質があります。この性質こそ、埋め込み/射影ペアを定義する ものです。つまり、写像e : X → Yとp : Y → Xの組が、p(e(x)) = x (x∈X) という等式を満たすとき、eとpは‘埋め込み/射影ペア’だというのです。e は単射でpは全射ですが、これは定義から導くことができます(興味と気力が ある方は、自力で導いてみてください)。

この形で埋め込み/射影ペアを定義すると、(x, y)→(x, y, 1)とか(x, y)→ (x, y, 0)に限らず、任意の2変数関数fを使った(x, y)→(x, y, f(x, y))も埋 め込みとして採用できます。つまり、2次元の平面を、3次元空間内の平らな平 面として埋め込むのではなく、グニョグニョした曲面として埋め込んでもいい わけですね。

別な例を挙げましょう。実数xに対して、小数部を切り落とした整数値を ceilInt(x)とします。整数nを同じ値の実数と見なす写像 (例:整数3→実数3.0)を、n→real(n)とすると、ceilInt(real(n)) = n が 成立します。つまり、realとceilIntの組も埋め込み/射影ペアとなっていま す。多くの場合、埋め込み/射影ペアの射影は、直積の射影(成分を取り出す 写像)ですが、この例のような埋め込み/射影ペアも存在します。(よく考え てみると、real, ceilIntの例も、直積とみなせるんですけどね、よく考えて みてください。)ただし、ceilIntのような写像までも射影と呼んでしまうの は抵抗があるので、別な呼び方があります。 それは次の「包含とレトラクト」のところで説明します。

・ 包含とレトラクト

BがAの部分集合であるとき、B⊆Aと書くのはご存じでしょう。このような (部分集合と全体集合の)関係が包含関係です。x∈Bに対して、同じxなんだ けど、「Aのなかのx」を対応させる写像 x → x を‘包含写像’と呼びます。 たとえば、神奈川県民⊆日本国民 ですよね。で、神奈川県民としての個人'山本栄 一郎'を、日本国民である'山本栄一郎'とみなす写像が包含写像です。包含写像を 単に‘包含’とも呼びます(混乱の心配がなければ)。

コンピュータでは整数と実数(浮動小数点数)はまったく別物ですが、数学 では「整数⊆実数」です。あるいは、「小数部がゼロである実数⊆実数」なら コンピュータでもOKでしょう。いずれにしても、整数→実数の“包含写像”を 考えて、逆向きの“小数部切り落とし写像”:実数→整数とペアにすると、先 に述べたように埋め込み/射影ペアとなっています。この場合、「射影」とい う言葉があまりシックリこないですね。そこで、「射影」の代わに‘レトラク ト’(retract)」と呼びます(レトラクトは「引っ張り込む」といった意味で す)。つまり、「包含/レトラクトペア」になっているのです。

埋め込み/射影ペアと包含/レトラクトペアは、ある抽象レベルではまっ たく同じ概念ですが、キモチの上では次のような違いがあります。

埋め込み/射影ペアや包含/レトラクトペアは、計算現象の世界ではあき れるくらいによく出現します。えっ? あまりお目にかかったことがないです か、だとしたら、これから気を付けて観察してみてください。

目次へ

8. 内部状態空間

・ 約束

注5
[int | value % 2 == 0]は、通常の集合論の記法だと、 {x∈int | x % 2 == 0 }と書かれる。

・ Point_* の(可能な)内部状態空間

→→スライド21:内部状態空間の見当の付け方

ほんとにその状態になるかどうかはとりあえずは考えない。ソース(の断片) から推測できる最大の範囲を考える。

次のような埋め込み/射影ペアが考えられる。(矢印は埋め込み方向)

しかし、次にような埋め込み/射影ペアは自然には作れない(人為的には作れ るが)。

次のような包含が考えられる。(記号「⊆」を使う)

埋め込み(射影を伴う)と包含の違いは次の点である。

  1. 埋め込みでは、写像方法がいくつかあり、なにかを特定しないと写 像が決まらない。
  2. 埋め込みの後で射影すると戻る。
  3. 包含では、自然な写像方法は1つしかなく、何もしなくても写像が決まる。

問: 埋め込み[int]×[int] → [double]×[double] に対して、人為的に射 影を作ってください。(「埋め込みの後で射影すると戻る」ことが条件)

NOTE: 問のヒント

このテキスト内に既に、[int]→[double] という埋め込みに対する射影の例 が出てきてます。先のノートを読まれた方は、この問の例は、「埋め込み/射影」と いう言葉より「包含/レトラクト」と呼んだほうが適切なことがお分かりでしょ う。

→→スライド22:この節のまとめです
NOTE: 内部状態空間が見える人は誰?

当日口頭では説明したものの、テキストでは強調されてない大事は点、それ は「内部状態空間は誰から見えるか」ということです。大部分の人にとって、 クラスの内部状態空間は謎です。見る手段がないのです。内部状態空間に関す る知識を持っている人は、当該のクラスを作ったプログラマ(実装者)です。 もう少し範囲を広げると、当該のクラスのソースコードを読める人ですね。

クラスごとに内部状態空間は確かに実在するのだけど大部分の人からは謎だ、 という事実はほんとに大事なことです。心にとめておいてください。次の節で、 このことを示す具体例を挙げています。

目次へ

9. 内部状態空間は外部から分かるか

→→スライド23:この指示にしたがってください

問:次のPoint_7の点は、座標(0.5, 1.7)に位置することができますか?

LIST13: 点その7
@sample("点その7:上下左右に整数シフトできる点")
class Point_7 {
 private double x, y;

 Point_7() {
  x = 0.0;
  y = 0.0;
 }

 double getX() {
  return x;
 }
 double getY() {
  return y;
 }

 void shiftX(long dx) {
  x += dx;
 }
 void shiftY(long dy) {
  y += dy;
 }
 // ...
}

問:次のPoint_8の点は、内部に実数座標を保持していることは外部から分か りますか。

LIST14: 点その8
@sample("点その8:上下左右に整数シフトできる格子点(2)")
class Point_8 {
 private double x, y;

 Point_8() {
  x = 0.0;
  y = 0.0;
 }

 long getX() {
  return Math.round(x);
 }
 long getY() {
  return Math.round(y);
 }

 void shiftX(long dx) {
  x += dx;
 }
 void shiftY(long dy) {
  y += dy;
 }
 // ...
}

問:次のFlag_1とFlag_2の内部の状態変数が違うことは外部から分かりま すか。

LIST15: フラグその1
@sample("フラグその1")
class Flag_1 {
 private boolean flag;

 Flag_1() {
   flag = false;
 }

 boolean isUp() {
   return flag;
 }

 void up() {
   flag = true;
 }
 void down() {
   flag = false;
 }
 void toggle() {
   if (flag) down();
   else up();
 }
}
LIST16: フラグその2
@sample("フラグその2")
class Flag_2 {
 private long n;
 /* nが偶数ならオフ、nが奇数ならオン */

 Flag_2() {
   n = 0;
 }

 boolean isUp() {
   return (n % 2 == 1);
 }

 void up() {
   if (!isUp()) n += 1;
 }
 void down() {
   if (isUp()) n += 1;
 }
 void toggle() {
   n += 1;
 }
}
→→スライド24:すぐ上にあるフラグの例に関して
→→スライド25:「外からは区別できない」の説明 1
→→スライド26:「外からは区別できない」の説明 2
NOTE: 外から区別できないなら同じだろう

この節のフラグの例(Flag_1, Flag_2)と上の3枚のスライドでは、「外から 区別できない」という概念を説明しています。前のノートで「大部分の人にとっ て、クラスの内部状態空間は謎だ」と注意しました(これは大事なことですよ!)。 内部状態空間を知るには、ソースコードを読むか、実行状態のプログラムをデ バッガなどで解析する必要があります。我々はこれから先、このような覗き見 はできない/しない前提で考えます。

クラスC1とクラスC2があり、同じアメナブルなインターフェースを持つとし ましょう。話を簡単にするため、アクセッサがa(), b(int i)、ミューテータ がm(boolean b), n()だとします。また、C1、C2は引数を持たないコンストラ クタC1(), C2()を持つとしましょう。

inst1= new C1();、inst2= new C2(); として、たとえば次のような値を考え ます。

  1. inst1.m(true); inst1.n(); inst1.m(false); return inst1.b(3);
  2. inst2.m(true); inst2.n(); inst2.m(false); return inst2.b(3);
この2つの値が同じであれば、C1とC2は似てる気がします。が、単なる偶然かも しれません。再びinst1, inst2を初期化して、別な操作をして値をとってみま す。
  1. inst1.n(); inst1.n(); return inst1.a();
  2. inst2.n(); inst2.n(); return inst2.a();
これも同じだったら、だいぶ似てる気がします。が、それにしても偶然かもし れません。

ところが、スライド25で述べている定義は、「任意のミューテータ呼び出し 列の適用」と「その後の任意のアクセッサ呼び出し」に関して得られる値が常に等 しいことを主張しています。こうなると、どうやってもC1とC2の差を指摘する ことができません。このことを‘振る舞い同値’とか‘観測的に識別不可能’ と呼ぶのです。

振る舞い同値な2つのクラスは、たとえばテスト担当者から見れば「同じクラス」 です。速度、メモリ使用量などの違いはあるかもしれませんが、C1が“正しい” ならC2も“正しい”はずであり、逆もまた真です。

NOTE: リファクタリングと互換な実装

リファクタリングは、外部仕様を一切変更せずにコードを改善することです。 改善というからには、ソースの可読性を上げるとか、構造をスッキリさせると かするのでしょう。が、ここでは、改善であれ改悪であれ、ともかく外部仕様 を一切変更せずにコードを変更することをリファクタリングとしましょう。

クラスC1をリファクタリングしたクラスをC2とします(クラス名が同じであっ ても、リファクタリング前/リファクタリング後を区別するために1と2の番号 でラベリングします)。すると、リファクタリングの定義からC1とC2は振る舞 い同値でなくてはなりません。もし振る舞い同値でなかったら、このクラスの 利用者(クライアント、カスタマ)に迷惑をかけることになります。

別な言い方をすると、リファクタリングは、振る舞い同値性を保ったままで クラス実装を変更することです。インターフェースを介してライブラリ/コン ポネントを利用している状況なら、「クラス実装を変更する」というよりむし ろ「クラス実装を取り替える」こともありますね。たとえば、JAXPからSAX APIを使った応用プログラムでは、SAXパーサーを取り替えても(原則的には) 影響がないはずです。

目次へ

観測と実験

よく見ること。刺激を与えて変化を確認すること。それが観測と実験です。

NOTE: 「外から見る」という立場

ここから先では、クラスの内部状態空間を知ることは完全にあきらめます。 内部状態空間は確かに存在します。しかし、それを知る手段は万人に開放され ていません。我々は、公開されているインターフェース(に含まれるアクセッ サとミューテータ)だけを使って、仕様やテストコードを記述するのです。

未知の内部状態空間に代わって“外的な(あるいは外在化した)状態空間” であるところの観測量空間を使うことにします。以下では、観測量空間の構成 が主な話題です。

10. アクセッサ、観測量空間、ミューテータ

NOTE: いまさらながらの定義

アクセッサとミューテータは、すでにスライドのなかで定義してますし、ア クセッサ/ミューテータ概念はもう使ってしまっています。以下の定義は今さ らながらですが、もとテキストでは、ここではじめて明示的な定義が登場しま す。

アクセッサ

次の条件を満たすメソッドを‘アクセッサ(accessor)’と呼ぶ。

  1. 基本型の値を返す(*注6)
  2. オブジェクトの内部状態だけで値が決まる。
  3. 呼び出しても、オブジェクトの内部状態へも環境へも影響を与えない。
注6

Javaのプリミティブ型だけでなく、StringやColorのような型も基本型と考え てよい。等号(equals)がハッキリと定義されているなら、基本型に採用してよ い。なお、等号は次の条件を満たす。

  1. x.equals(x)
  2. x.equals(y) ⇒ y.equals(x)
  3. x.equals(y) かつy.equals(z) ⇒ x.equals(z)

実用上の理由から、基本型はリテラル表現を持ち、生成が容易であることも 条件である。

観測量空間

すべてのアクセッサの戻り値の組み合わせの全体からなる集合を‘観測量空間’ と呼ぶ(*注7)。より詳細には:

注7

「相空間(phase space)」が力学やシステム理論の用語。「一般化座標」、 「相点」などの用語を使うが、なじみにくいので観測量空間、観測量、観測量 タプルとした。

・ 例

ミューテータ

次の条件を満たすメソッドを‘ミューテータ(mutator)’と呼ぶ。

  1. 値を返さない(void)。
  2. オブジェクトの内部状態を変更する。
  3. 環境に影響を与えない。

Point_7 のshiftX(long), shiftY(long)、Flag_1のup(), down(), toggle() などはミューテータである。

コンストラクタ、アクセッサ、ミューテータだけで構成されるクラス/オブ ジェクトを‘アメナブル(amenable)’なクラス/オブジェクトと呼ぶ。

アメナブルであることを前提に、クラスのインターフェース(メソッド仕様) を次のように書く(ここだけの記法)。

spec Flag {
 constructor:
  Flag();
 accessor:
  boolean isUp();
 mutator:
  up();
  down();
  toggle();
}

次の書き方は、一応は合法なJava構文となっている(コメント内の記述に意 味を持たせているのでインチキだが)。また、Javaのコンストラクタはクラス と同じ名前にする約束なので、仕様では名前を指定できない。

@spec interface Flag {
 /*-- constructor:
  Flag();
 */

 /*-- accessor: */
  boolean isUp();

 /*--  mutator: */
  void up();
  void down();
  void toggle();
} 

目次へ

11. 観測量空間の作り方

NOTE: 急ぎ足だったな

JavaWorld DAY当日の講演では、このへんから、時間が押してきて急ぎ足になっ てしまいました。受講者の皆さんにはツライところだったと思います(私もツ ラかった)。 とりあえず、以下の(もとテキストの)説明を読んでみてください。この節 の最後に、スライドとその解説を付け加えます。

各種の点(Point)の例では、内部状態空間と観測量空間がほぼ同じだったが、 いつでも内部状態と観測量空間が同じわけではない。

TABLE: 内部状態空間と観測量空間
サンプル 内部状態空間 観測量空間 注意
Point_7 [double]×[double] [double]×[double] 実質的に[long]×[long]
Point_8 [double]×[double] [long]×[long]丸めている
Flag_1 [boolean] [boolean] そのまま
Flag_2 [long] [boolean] 偶数/奇数の別

アクセッサのインターフェースだけから、形式的な観測量空間を作れる。形 式的な観測量空間は、特定の観測量タプルが実際に観測されるかどうかは無視 して、考えられるすべての観測量タプルを集めたもの。

LIST17: 点その9
class CoordException extends Exception {}

@sample("点その9")
class Point_9 {
  private double x, y;

  Point_9() {
    x = 0.0;
    y = 0.0;
  }
  double getCoord(int i) throws CoordException {
    if (i == 1) return x;
    if (i == 2) return y;
    throw new CoordException();
  }
  // ...
}

この例では、「double getCoord(1)」と「double getCoord(2)」が2本の“座 標軸”を定める。これは、「double getCoord1(); double getCorrd2();」と しても同じだし、名前の好みを除外すれば「double getX(); double getY();」 とも同じ。

すべてのアクセッサに特定の引数を与えた“個数”だけの“座標軸”がある と考える。この“個数”が形式的観測量空間の次元である。

LIST18: 配列に似たデータ
import java.util.*;

@sample("配列に似たデータ")
class DoubleArrayLikeData {
 private Map map;

 DoubleArrayLikeData() {
   map = new HashMap();
 }

 double item(int n) {
  if (n < 0) return 0.0;
  Double d = (Double)map.get(n);
  return d == null? 0.0 : d;
 }

 void set(int n, double val) {
   map.put(n, val);
 }
}

この例は、正負両方向に無限に伸びるdouble配列を意図している。よって、 座標系=座標軸のセットは「double item(0); double item(1); double item(-1); double item(2); …」となる。 「double item_0(); double item_1(); double item_minus1(); double item_2(); …」などと考えればよい。

ほんものの配列では、有効な座標軸はlengthに依存する。lengthの値ごとに 0次元空間、1次元空間、2次元空間、3次元空間、…の観測量空間が存在すると 考えればよい。

→→スライド27:座標の概念
→→スライド28:状態を表す(と期待できる)“座標”
NOTE: 説明がツラかったスライド27,28

当日、スライド27,28のあたりの説明は一番ツラかったですね。「分かりにく いだろうな」と思いながらも、ていねいに説明している時間は全然ないし……

で、何を言いたかったかというと、内部状態空間を直接知るすべはなくても、ア メナブルなインターフェースにおけるアクセッサが座標の役割を果たす、とい うことです。そもそも、「座標」という言葉に曖昧性があって困るんですよ。 たとえば、3次元の幾何学的点(geometirc point)に対して、x座標、y座標、 z座標のようなスカラー値の座標成分と、それらをまとめたタプル(x, y, z)の どちらを指すかハッキリしない。以下では、できるだけ「座標成分」と「座標 タプル」という言葉を使い分けることにします。

さて、問題のスライド27,28の内容を、別な形で説明します。pが3次元空間の 幾何学的点を表すオブジェクトだとして、p.getX(), p.getY(), p.getZ()という3つのアクセッサ値が、通常の意味で“pの座標”を 与えます。幾何学的な点でなくても、アクセッサ値をそのオブジェクトの座標 成分と考えよう、と私は言いたいのです。幾何学的点では、座標は位置を示しますが、 一般にはアクセッサ値(のタプル)は状態を示していると考えることができま す。ただし、注意しなくてはならないのは、アクセッサ値が内部状態空間を正 直に表していることは保証できないことです。何度も強調しているように、内 部状態空間は所詮は未知の空間ですから、「アクセッサ値(のタプル)は、状態を、 間接的かもしれないが示している」と期待する以外にできることはないのです(内部状態空間への直接アク セスはあきらめていますからね)。

引数なしのアクセッサは、そのまま1つの座標成分、あるいは座標軸(名前と 値の領域)を定義しているとみなせますが、引数を持つアクセッサが問題にな ります。しかし、引数付きのアクセッサも、引数なしのアクセッサを集めたも のと解釈できます。たとえば、長さ5のdouble値の配列(のようなデータ)が あったとき、 double item(int i); という引数付きアクセッサから得られる有効な(例外と ならない)情報は、item(0), item(1), item(2), item(3), item(4)ですね。 これは、item0(), item1(), item2(), item3(), item4()という5つの引数なし アクセッサの集まりと事実上同じです。

つまり、引数付きのアクセッサがあったとしても、引数値を固定した ものを1つのアクセッサとして考えれば、たくさんの引数なしアクセッサに分解できて しまいます。よって我々は、(莫大な数になるかもしれないけど)引数なしの アクセッサだけを考えれば十分です。1つの引数なしアクセッサが1つの座標成 分に対応します。アクセッサがうんとタクサンあると、座標タプルはうんと長 くなります。“座標タプルの空間=観測量空間”の次元はうんと高くなります。

空間の次元が高くなれば、取り扱いは大変になりますが、すでに紹介した射 影、埋め込み、包含などの概念はそのまま使えます。幾何学的/力学的な定式 化もそのまま使えます。そういう意味では、次元が高くなっても別にどうって ことないのです。

→→スライド29:観測量空間
NOTE: 観測量空間は構文的に決定される

クラスの内部状態空間は謎ですが、観測量空間は公開されているインターフェー スを見ただけですぐさま決定できます。たとえば次の例を考えましょう。

interface Hoge {
 /* アクセッサ */
 double foo();
 String bar(long x);
 /* ミューテータ */
 void baz();
}

アクセッサはfooとbarですが、barは引数を持つので、longの値ごとに bar(0), bar(1), bar(-1)のように分解して考えます。long値の総数はタクサン あるので、観測量空間は、 [double]×[String]×[String]×…(タクサン)×[String]となります。クラ スHogeのオブジェクトは、ある時点で観測量空間の一点を占めます。この一点 を特定するには、タクサンの成分を持ったタプルが必要ですが、アクセッサは 副作用なしに何度でも呼べるので、根気さえあれば観測量空間の一点を特定 できます。

→→スライド30:オブジェクトは軌跡を描く
NOTE: プログラム vs 力学

プログラム(計算現象)の概念と、力学の概念の対応関係は次のようになります。

TABLE: プログラム vs 力学
プログラム 力学
オブジェクト 質点
アクセッサ 座標成分
観測量タプル 位置
観測量空間 座標が入った空間
ミューテータ 外力
振る舞い 運動の軌跡

オブジェクトの内部状態は分からないが、アクセッサの値は観測にかかりま す。すべてのアクセッサ値をタプルにすれば、内部状態の代わりとなる情報が 得られますね。それが観測量空間の点(タプル)です。何もしなければ、オブ ジェクトは一定の状態 -- したがって観測量空間の同一点にとどまるでしょう。 ミューテータ呼び出しで外力(刺激)を加えてやると、状態遷移を起こすので、 観測量タプルも変化します(たまたま変化しないこともあるけど)。

以上のような状況を絵に描いてみたのがスライド30です。「実際の状態の領 域」と書いてあるのは、正確には「実際の状態の領域に対応する観測量空間の 領域」です。

→→スライド31:刺激反応系としてのオブジェクト
NOTE: 刺激反応系に対する法則

今まで物理的な概念/用語法を拝借してきたのですが、ここでは生物学や心 理学の用語法を使ってます。オブジェクトを生き物のように考えると、ミュー テータはそれに刺激を与える道具です。たとえば、ナメクジをつっついてみる とか、塩をかける(死んでしまいます)とかです。

ミューテータを組み合わせていろいろな刺激を与えたとき、対象の 観測可能な状態(観測量タプル)がどう変化するか、それを記述すると、対象 の特徴を捉えることになります。その記述が十分に精密なら、法則と呼んでも いいでしょう。計算現象の世界にいる生き物(オブジェクト)に関する、刺激 と反応の法則、それが仕様というわけです。

目次へ

/* まだ未完成です。*/

/* 続きが予定さています。*/

/* ちょっと疲れました。増補版テキストの続きは、2,3日休んでからですね。 ここまで来たから、最後まで“増補”するつもりですけど。*/