vieweditattachhistoryswikistopchangessearchhelp

Squeak の workspace で BankAccount

Io のサンプルスクリプトを受けて
--sumim

なお、Squeak の GUI を使った(ツール群のサポートを受けた)まっとうな BankAccount は こちら [英語]。
ちなみにこのページの筆者であるジョン・マロニーさんは、SELFMorphic を作り、それを Squeak に移植した人です。--sumim


とりあえずシステムには何も手を加えずにスクリプト言語ライクに BankAccount

Smalltalk は普通、Ruby などでするようにクラス定義をスクリプト中で云々というようなことはしませんが、そういうこともできなくもないということで。

Squeak の workspace を開き(デスクトップメニュー → open... → workspace )、下のスクリプトをコピペして、逐次評価します。
複数行に渡るメッセージ式は、最終行の行末のピリオドを頼りに判断してください。
メッセージ式を選択して返値を期待しないもの("==> ほにゃらら" が無い文)は Alt/Cmd-d で do it(評価)。
返値を期待するものは Alt/Cmd-p で print it します。
print it と do it の違いは評価式直後への返値の挿入の有無だけなので、すべて print it でも害はありません。
返値が明示的にされていないメソッドを起動する式(文的な性格を持つ)ではデフォルトでレシーバが返されるので、それがインスタンスなら不定冠詞+クラス名に、クラスならその名称のまま文字列化されて挿入されます。
print it 時に自動挿入された文字列は、選択状態にあるので、確認後、不要ならキーボードの delete キーで削除しもとの状態に戻すことができまます。

メッセージ式が一行に収まっているものは、選択の必要はありません。
行内のどこでもキャレットを置いてから do it / print it すると評価の前に、該当行を自動選択してくれます。--sumim
Smalltalk at: #sgetterGenerator put:   "この行から"
	[ :class | class  instVarNames do: [ :slot | 
		{slot, String cr, String tab, '^ ', slot.
		slot, ': x', String cr, String tab, slot, ' := x'} do: [ :code |
			class compile: code classified: 'accessing']]].  "この行までが一文"

Object subclass: #BankAccount   "この行から"
	instanceVariableNames: 'dollars'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Category-BankAccount'.  "この行までが一文"

Utilities authorInitialsPerSe isEmptyOrNil ifTrue: [Utilities setAuthorInitials: 'your initial'].

sgetterGenerator value: BankAccount.

"この行から"
#('deposit: x
	self dollars: self dollars + x' 'accessing'
'withdraw: x
	self dollars: (0 max: (dollars - x))' 'accessing'
'initialize
	dollars := 0' 'initialize') 
pairsDo: [ :code :category | 
	BankAccount compile: code classified: category].   "この行までが一文"

Smalltalk at: #account put: (BankAccount new initialize; yourself).
account dollars: 200.
account dollars        "==> 200"
account deposit: 50.
account dollars.       "==> 250"
account withdraw: 100.
account dollars.       "==> 150"
account withdraw: 200.
account dollars.       "==> 0"

BankAccount subclass: #StockAccount   "この行から"
	instanceVariableNames: 'numShares pricePerShare'
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Category-BankAccount'.   "この行までが一文"

sgetterGenerator value: StockAccount.

"この行から"
#('dollars
	^ self numShares * self pricePerShare' 'accessing'
'dollars: x
	self numShares: x asFloat / self pricePerShare' 'accessing'
'initialize
	numShares := 10.
	pricePerShare := 30' 'initialize') 
pairsDo: [ :code :category | 
	StockAccount compile: code classified: category].   "この行までが一文"

Smalltalk at: #stock put: (StockAccount new initialize; yourself).

stock numShares.       "==> 10"
stock pricePerShare.   "==> 30"
stock dollars.         "==> 300"
stock dollars: 150.
stock numShares.       "==> 5.0"

stock dollars: 600.
stock dollars.         "==> 600.0"
stock numShares.       "==> 20.0"
stock deposit: 60.
stock dollars.         "==> 660.0"
stock numShares.       "==> 22.0"



もうすこし読み下し(書き下し)やすくする改変をシステムに加えて BankAccount

上のスクリプトで、インスタンスへのメッセージ送信はいいとして、クラスやメソッドの定義が(スクリプト言語のお手軽さに比べると)ごちゃごちゃしてわかりにくいという印象を持ちました。

もともと想定されていない使い方とはいえ、このままでは肝心の BankAccount で何をしているのかがつかめません。そこで、いくつかメソッドを追加して、すっきり書けるようにしてみました。
これらの機能は、UserScriptionHelping.cs を file in 後、使用できるようになります。
下のスクリプトでは第一行がそれを行なうためのものです。
UserScriptionHelping.cs は起動に用いた仮想イメージ(.image)と同じディレクトリにあらかじめ置いておく必要があります。--sumim
(FileStream oldFileNamed: 'UserScriptionHelping.cs') fileIntoNewChangeSet.

#BankAccount << Object
	attribute: #dollars;
	define: 'initialize' as: 'dollars := 200';
	define: 'deposit: x' as: 'self dollars: self dollars + x';
	define: 'withdraw: x' as: 'self dollars: (0 max: self dollars - x)'.

#account <== (BankAccount new initialize; yourself).
account dollars.       "==> 200"
account deposit: 50.
account dollars.       "==> 250"
account withdraw: 100.
account dollars.       "==> 150"
account withdraw: 200.
account dollars.       "==> 0"

#StockAccount << BankAccount
	attribute: #numShares;
	attribute: #pricePerShare;
	define: 'initialize' as: 'numShares := 10. pricePerShare := 30';
	define: 'dollars' as: '^ self numShares * self pricePerShare';
	define: 'dollars: x' as: 'self numShares: x asFloat / self pricePerShare'.

#stock <== (StockAccount new initialize; yourself).
stock numShares.    "==> 10"
stock dollars.      "==> 300"
stock dollars: 600.
stock numShares.    "==> 20.0"
stock deposit: 60.
stock dollars.      "==> 660.0"
stock numShares.    "==> 22.0"


スクリプト中、行末に「;」がある文(正確には式)は、カスケードと呼ばれるもので、レシーバを一番目の式のそれに固定して畳みかけるようにメッセージを送信するときに使います。たとえば、
#BankAccount << Object
	attribute: #dollars;
	define: 'initialize' as: 'dollars := 200';
	define: 'deposit: x' as: 'self dollars: self dollars + x';
	define: 'withdraw: x' as: 'self dollars: (0 max: self dollars - x)'.
は、#BankAccount << Object の返値(BankAccount に束縛されたオブジェクト)に対して、
2行目、3行目…とメッセージを送ります。したがってこの文は、
#BankAccount << Object.
BankAccount attribute: #dollars.
BankAccount define: 'initialize' as: 'dollars := 200'.
BankAccount define: 'deposit: x' as: 'self dollars: self dollars + x'.
BankAccount define: 'withdraw: x' as: 'self dollars: (0 max: self dollars - x)'.
という複数の文(式)を連続して一気に評価したのと同じ意味を持ち、
通常は、こちらのほうが分かりやすいので(「;」を使わずこのように書くことが)推奨されます。
ここでは定義文っぽく見せるために、あえて「;」を使ってみました。--sumim


プロトタイプベースで BankAccount

上に少々手を加えると、プロトタイプベース・オブジェクト指向スクリプティングが可能になります。

PrototypeScripting.cs(上の UserScriptionHelping.cs とは共存できません)

インスタンス化の要不要で、読み下しやすさにも微妙に差が出ますね。
プロトタイプベースは(文法がメッセージ送信メタファに十分配慮されていれば、あるいは用いた記法をパース/生成するための脳、たとえばS式用のリスパー脳、メッセージ式用の Smalltalk 脳、を持っていれば)表現したいことをすなおに書き下せるぶん、書き捨てのスクリプティングにはクラスベースより向いているかもしれません。--sumim
(FileStream oldFileNamed: 'PrototypeScripting.cs') fileIntoNewChangeSet.

(#BankAccount asClone: Object)
	attribute: #dollars value: 200;
	define: 'deposit: x' as: 'self dollars: self dollars + x';
	define: 'withdraw: x' as: 'self dollars: (0 max: self dollars - x)'.

BankAccount dollars.       "==> 200"
BankAccount deposit: 50.
BankAccount dollars.       "==> 250"
BankAccount withdraw: 100.
BankAccount dollars.       "==> 150"
BankAccount withdraw: 200.
BankAccount dollars.       "==> 0"


#MyAccount asClone: BankAccount.
MyAccount deposit: 500.
MyAccount dollars.         "==> 500"
BankAccount dollars.       "==> 0   # プロトタイプのスロットには影響なし。"

(#StockAccount asClone: BankAccount)
	attribute: #numShares value: 10;
	attribute: #pricePerShare value: 30;
	define: 'dollars' as: '^ self numShares * self pricePerShare';
	define: 'dollars: x' as: 'self numShares: x asFloat / self pricePerShare'.

StockAccount dollars.      "==> 300"
StockAccount dollars: 150.
StockAccount dollars.      "==> 150.0"
StockAccount numShares.    "==> 5.0   # 株数値が変更されている。"

#MyStock asClone: StockAccount.
MyStock dollars: 600.
MyStock numShares.         "==> 20.0"
MyStock deposit: 60.
MyStock dollars.           "==> 660.0"
MyStock numShares.         "==> 22.0"
MyStock withdraw: 120.
MyStock dollars.           "==> 540.0"
MyStock numShares.         "==> 18.0"


先の #BankAccount << Object の行で ( ) がなくて、この #BankAccount asClone: Object で ( ) がないといけない理由のひとつは、 #<< Object(二項メッセージ)と asClone: Object(キーワードメッセージ)の優先順位の違いです。

二項メッセージ(記号のみで構成されるメッセージセレクタとそのパラメータ)は、キーワードメッセージ(アルファベット数字文字列+「:」の一回以上の繰り返しで構成されるメッセージセレクタと「:」の直後に挿入された「:」の数と同数のパラメータにより構成されるメッセージ)に優先して評価されます。

したがって、キーワードメッセージのレシーバを生み出す式によく用いるメソッドは、パラメータひとつのキーワードメッセージより二項メッセージセレクタを使って定義しておくほうが ( ) を省略できてなにかと便利です。

もっとも使える記号は限られているので、したいことがうまく伝わるセレクタにできる確率は非常に低いですが。

ちなみにアルファベット数字文字列で「:」が最後に付かない、つまり、パラメータを付さないメッセージを単項メッセージと呼びます。
なお、単項メッセージは他の2つのタイプのメッセージに優先してレシーバに送信されます。
(レシーバに相当するものが式なら、それが先に評価されます。)

またキーワードメッセージを連続して送る場合、たとえば、a: arg を送って返ってきた結果に b: brg を送りたいときは、(object a: arg) b: arg のように、先行する式の優先度を ( ) で括って明示的にしてやらなければいけません。なぜなら、object a: arg b: brg と書くと文脈的に、#a:b: というセレクタの a: arg b: brg というメッセージを送りたいのだと解釈されてしまうからです。
もちろん、二項メッセージの場合も、( ) でくくって冗長でも明示的にするほうが親切です。
(さすがに単項メッセージでは無用でしょう…。)
私は親切ではないので、括弧は付けませんでした。嘘です。先の例では、やはり宣言文っぽく見せるためにあえて付けませんでした。--sumim

このページを編集 (11996 bytes)


Congratulations! 以下の 4 ページから参照されています。

This page has been visited 5287 times.