言語ゲーム

とあるエンジニアが嘘ばかり書く日記

Twitter: @propella

自分で ThingLab のオブジェクトをつくる。

ThingLab では、既存のオブジェクトを組み合わせて簡単に新しいオブジェクトを作る事が出来る一方で、全く新しいオブジェクトを一から作るのはちょっと難しい。オブジェクトを作る為のツールが用意されていないばかりか、Smalltalk のマニアックな機能を沢山使って作られているため、Smalltalk 自身が持つ IDE の機能も役に立たないからだ。(多分)そこで、既存のクラスのソースを元に新しいクラスを作成してみる。

注) ThingLab においては、クラスとオブジェクト(インスタンス)の関係は普段の Squeak 以上に密接に繋がっている。ThingLab クラスはそれぞれ prototype というクラスインスタンス変数を持ち、その中にプロトタイプと呼ばれるオブジェクトを一つずつ持つ。ThingLab ブラウザでは、そのオブジェクトを操作する事によりクラス定義を変更する事が出来る。ThingLab のクラス定義では、クラスそのものの定義と同時にプロトタイプの定義(具体的な座標の値など)も行う。

まず、MidPointLine のソースを見よう。Things/MidPointLine.st がそれだ。

ThingLabObject subclass: #MidPointLine
	instanceVariableNames: 'line point '
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Prototypes'!

MidPointLine prototype parts: 'line point'.
MidPointLine prototype field: 'line' replaceWith: (40@40 line: 100@20).
MidPointLine prototype field: 'point' replaceWith: 70@30!

MidPointLine prototype inserters: #('line point1'  'line point2').
MidPointLine prototype constrainers: #('line')!

Constraint owner: MidPointLine prototype
	rule: 'point = ((line point1 + line point2) // 2)'
	error: 'line location dist: point'
	methods: #('self primitiveSet.point: (line point1 + line point2) //2'
		'line primitiveSet.point2: line point1 + (point-line point1*2)'
		'line primitiveSet.point1: line point2 + (point-line point2*2)' )!

クラス定義の後に、メソッド定義ではなく ThingLab のプロトタイプ定義がある。論文には型チェックの定義も出来るような事が書いてある。その後の制約定義が面白い。

制約定義はなんとなく直感で意味がわかると思うが、満たされるべきルールと満たすためのメソッド、そして諦める時の?エラーから成る。line point1 というような構文は path と言って、ThingLab 独特の物だ。point1 なるメソッドはどこにも無いのにメンバアクセサはデフォルトで使えるようになっている。また要素の代入は primitive.メンバ名: を使う。これらの機能はリフレクションを利用している為、Smalltalk ブラウザには現れない。

これを元に新しいクラスを作ってみた。仕様は次の通り

  • point1 から point2 へ直線を引く。
  • 線を3等分し、point1 に近いほうに点(point) を打つ。

制約の式はややこしく見えるが、ようは point = (2 * point1 + point2) / 3 を変形してあるだけだ。右を動かした場合、左を動かした場合、中の点を動かした場合に備えて3つのメソッドが必要だ。

ThingLabObject subclass: #Trisect
	instanceVariableNames: 'line point '
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Prototypes'!

Trisect prototype parts: 'line point'.
Trisect prototype field: 'line' replaceWith: (10@10 line: 100@100).
Trisect prototype field: 'point' replaceWith: 40@40 !

Trisect prototype inserters: #('line point1'  'line point2').
Trisect prototype constrainers: #('line')!

Constraint owner: Trisect prototype
	rule: 'point = ((2 * line point1 + line point2) // 3)'
	error: 'line location dist: point'
	methods: #('self primitiveSet.point: (2 * line point1 +  line point2) //3'
		'line primitiveSet.point2: 3 * point - (2 * line point1)'
		'line primitiveSet.point1: (3 * point - line point2) / 2' )!