プライベートメソッドと逆の振る舞いを Squeak Smalltalk のメソッドに追加する

#はじめに

　ちょっと前に見つけたインディアン・ポーカーの問題
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">某所で知ったこの問題。単純な割に型をどう定義するか、処理をどこに置くか考えさせられて楽しかった。DDD、DCI向けにも良い例になるかも。条件を変えるとうまく成立しない等あったので勝手ながら一部変更してみた→コーディング試験を受ける | リクルートコミュニケーションズ<a href="https://t.co/BZ9P48Xz9k">https://t.co/BZ9P48Xz9k</a> <a href="https://t.co/YPTRwI1KAF">pic.twitter.com/YPTRwI1KAF</a></p>&mdash; sumim (@sumim) <a href="https://twitter.com/sumim/status/1043665621433470977?ref_src=twsrc%5Etfw">2018年9月23日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

　これを実装するにあたって、どんなクラス設計がよいのかなぁ…などと考えていたとき、ふと“自分の額にかざした（つまり自分は見ることができない）カード”をどのようにプロパティとして表現したらよいのだろう？というのが気になりました。

　このとき最初に思いついたのが「プライベートメソッドと逆の振舞い」です。

　Pharo Smalltalk からは除かれてしまっていますが、Squeak Smalltalk には `pvt` で始まるメソッドは `self` をレシーバーにしないとコールできないというコンパイル時制約が設けられています。Ruby で、レシーバーを省略しないと（ニアリーイコール、レシーバーが `self` でないと）コールできない private なメソッド属性と似た考え方ですね。

　であれば、これの逆、つまり `self` をレシーバーにできないメソッドを定義できれば、たとえば額にかざして自分は見てはいけないようなプロパティに誤ってアクセスしようとしても、（間接アクセス・パターン使用時に限りますが─）コンパイルが通らないというようなことが可能になるはずです。

#Squeak Smalltalk の入手とインストール、起動

　Squeak Smalltalk処理系は [squeak.org](https://squeak.org/) から入手できます。2018年12月現在のバージョンは 5.2 です。

　各プラットフォーム向けの `.zip`（macOS は `.dmg`）、もしくは主要３プラットフォーム向け VM が同梱された `All-in-One.zip` をダウンロードして展開（macOS は アプリケーションフォルダにコピー）すればインストールは完了です。

　`.image` ファイルを、そのプラットフォームの実行形式ファイルである仮想マシン（Windows なら `Squeak.exe`）にドロップインすれば起動できます。macOS ではコピーした `Squeak5.2-nnnnn-64bit.app` をそのままダブルクリックで起動できます。

　ちょっと遊ぶだけなら、初回起動時にでるメッセージはスルー（`Skip` を選ぶか、デスクトップの適当な場所をクリック）でよいでしょう。

![sq52startup.png](https://qiita-image-store.s3.amazonaws.com/0/49275/87ef11c7-ecfc-ce04-8c30-c0c99089f724.png)<br/>Squeak 環境の初回起動時の画面


#Squeak Smalltalk のプライベートメソッドの挙動

　改めて、Squeak Smalltalk のプライベートメソッドの挙動（より厳密にはプライベートメソッドをコールするメッセージ式を含むメソッドをコンパイルしたときの挙動）を見てみましょう。

　Squeak Smalltalk で `pvt` で始まるメッセージを `self` 以外をレシーバーにしてコンパイルしようとすると、次の一連の図にあるようにインライン警告が出てコンパイルが通りません。

![11917.png](https://qiita-image-store.s3.amazonaws.com/0/49275/473267cb-cf37-ccb3-195e-b80c82e1fdbd.png)<br/>▸`0 pvtMessage` という式を評価（print it など）しようとした場合…

![11918.png](https://qiita-image-store.s3.amazonaws.com/0/49275/1ec534cc-704e-6439-5ca7-e006981250d6.png)<br/>▸`pvtMessage` メソッドが未定義なので疑問を呈してきますが、別に間違っていないよ！と `Choose` を選んでコンパイル作業を続行させようとすると…

![11919.png](https://qiita-image-store.s3.amazonaws.com/0/49275/24ade1d6-69d1-de2f-e7f9-b9dfaff2846e.png)<br/>▸“プライベートメッセージは `self` がレシーバーじゃないとコンパイルはまかりならん！”と叱られます

![11920.png](https://qiita-image-store.s3.amazonaws.com/0/49275/a2748ce0-8322-afe7-8af9-51d8bc0e0a52.png)<br/>▸もとより、`self pvtMessage` なら何の問題も無くコンパイルは通り、コードは実行されて無事(?) `MessageNotUnderstood` 例外があがります。

#Squeak Smalltalk のプライベートメソッドの実装

　ではこの振る舞いの実装はどうなっているのでしょうか？

　コンパイル時にインラインで表示された警告 `Private messages may only be sent to self` のうち、特徴的な `Private messages` の部分を選択して、右クリック → `more...` → `method strings with it (E)` を選択すると、当該警告を表示する処理と関わりの深いメソッド `MessageNode>>pvtCheckForPvtSelector:` の定義を呼び出せます。

![11921.png](https://qiita-image-store.s3.amazonaws.com/0/49275/f1f0b802-727c-94b2-6377-e57be9bd8dbd.png)<br/>▸`MessageNode>>#pvtCheckForPvtSelector:` の定義

　どうやら、セレクターがプライベート属性を持つ（つまり、`pvt` で始まる）場合、もしそうなら、レシーバーが擬変数 `self` でないかについて、抽象構文木からコードを生成する途中でチェックを行ない、これにひっかかれば件のインライン警告を出すしくみのようです。

　従ってこの逆、つまりセレクターが自己排除属性を持つ（仮に `xvt`で始まる）にも関わらず、レシーバーが `self` である場合のチェックをし、そうなら警告を出す`MessageNode>>#xvtCheckForXvtSelector:` を新たに定義すれば目的は達成できそうです。



#プライベートメソッドと逆の振る舞いを実装する

　まず無害な `isXvtSelector` から実装しましょう。

　先ほど呼び出した `MessageNode>>#pvtCheckForPvtSelector:` の定義を表示しているウインドウにある `implementors` ボタンをクリックすると、その定義中でコールされているメソッド名（セレクターと言います）の一覧が出てくるので、その中から `isPvtSelector` を選びます。

![11922.png](https://qiita-image-store.s3.amazonaws.com/0/49275/07340346-1d5e-fb21-f9f0-698670ea5a6e.png)<br/>▸メソッド中でコールされているメソッドの一覧から `isPvtSelector` を選ぶ

　すると、２つの同名メソッドが存在することがわかります。

![11923.png](https://qiita-image-store.s3.amazonaws.com/0/49275/3fed3cb1-54fd-c348-ad14-8c4a2b958cc8.png)<br/>▸`isPvtSelector` の実装が２つ見つかる

　本来ならきちんと調べないといけないのですが、たぶん（ぉぃぉぃ…） `MessageNode>>#pvtCheckForPvtSelector:` でコールしているのは `SelectorNode>>#isPvtSelector` の方で、その中でさらにコールされているのが `Symbol>>#isPvtSelector` なのでしょう。きっと。^^;

　これらをベースに、それぞれ `SelectorNode>>#isXvtSelector` と `Symbol>>#isXvtSelector` を定義して追加します。

　`SelectorNode>>#isXvtSelector` は簡単です。最初の行を含めてメソッド中に登場する `Pvt` を２つとも `Xvt` と書き換えるだけです。

　コメントもそれっぽく書き換えたら、alt + s もしくは右クリック → `accept (s)` でコンパイルします。

```smalltalk:SelectorNode&nbsp;>>
isXvtSelector
	"Answer if this selector node is a self-exclusive message selector."

	^key isXvtSelector
```

![11924.png](https://qiita-image-store.s3.amazonaws.com/0/49275/9e38af2e-3dcd-8ef1-6dec-0c7447d4c249.png)<br/>▸`SelectorNode>>#isXvtSelector` のコンパイル

　コンパイル後、表示は元の `SelectorNode>>#isPvtSelector` のものに戻ってしまいますが、ちゃんと `SelectorNode>>#isXvtSelector` は追加されているので安心してください。初回コンパイル時、イニシャルを求められるのでこれも適当に教えてやってください。

　続けて上の枠から `Symbol>>#isPvtSelector` の定義を選択して表示を切り替え、これも次のように書き換えて alt + s もしくは `accept (s)` でコンパイルします。

```smalltalk:Symbol&nbsp;>>
isXvtSelector
	"Answer whether the receiver is a self-exclusive message selector, that is,
	begins with 'xvt' followed by an uppercase letter, e.g. xvtStringhash."

	^ (self beginsWith: 'xvt') and: [self size >= 4 and: [(self at: 4) isUppercase]]
```

　あとは、再び `MessageNode>>#pvtCheckForPvtSelector:` の定義を表示しているウインドウに戻って、同じような要領で `MessageNode>>#xvtCheckForXvtSelector:` を定義すれば準備は終わりです。

```smalltalk:MessageNode&nbsp;>>
xvtCheckForXvtSelector: encoder
	"If the code being compiled is trying to send a self-exclusive message (e.g. 'xvtCheckForXvtSelector:') to self, then complain to encoder."

	selector isXvtSelector ifTrue:
		[receiver isSelfPseudoVariable ifTrue:
			[encoder notify: 'Self-exclusive messages may only be sent to other than self']].
```

　このコードをコピペせず、ご自身で編集される場合は、`receiver isSelfPseudoVariable` のチェックを `ifFalse:` から `ifTrue:` に変えておくのをお忘れなく！

　最後に、`senders` ボタンを押して一覧から `pvtCheckForPvtSelector:` を選び、そのコール元の定義を呼び出します。

![11926.png](https://qiita-image-store.s3.amazonaws.com/0/49275/9e9a0494-de01-14b2-0a1c-98f07552d32b.png)<br/>▸`senders` で `pvtCheckForPvtSelector:` のコール元の定義を呼び出す

![11927.png](https://qiita-image-store.s3.amazonaws.com/0/49275/7e510aed-9f43-bdf3-9d55-5e4d0bd7d364.png)<br/>▸`pvtCheckForPvtSelector:` をコールしている `MessageNode>>#receiver:selector:arguments:precedence:from:` メソッドの定義


　最後に式の区切りであるピリオドを追加し、続く行に `self xvtCheckForXvtSelector: encoder` と追加してコンパイルすれば完了です。

```smalltalk:MessageNode&nbsp;>>
receiver: rcvr selector: aSelector arguments: args precedence: p from: encoder 
	"Compile."

	self receiver: rcvr
		arguments: args
		precedence: p.
	originalSelector := aSelector.
	self noteSpecialSelector: aSelector.
	(self transform: encoder)
		ifTrue: 
			[selector isNil ifTrue:
				[selector := SelectorNode new 
								key: (MacroSelectors at: special)
								code: #macro]]
		ifFalse: 
			[selector := encoder encodeSelector: aSelector.
			rcvr == NodeSuper ifTrue: [encoder noteSuper]].
	self pvtCheckForPvtSelector: encoder.
	self xvtCheckForXvtSelector: encoder
```

　動作を検証するには、改行を追加→直後に削除するなどして変更を加えた後、再コンパイル（alt + s もしくは、右クリック → `accept (s)`）してみればよいでしょう。うまく動作しているなら、次図のようにコンパイルは拒否されるはずです。

![11928.png](https://qiita-image-store.s3.amazonaws.com/0/49275/eac965ea-5a4b-ab2e-117f-9415ee58fcdb.png)<br/>▸`xvt` で始まるメソッドを `self` をレシーバーにしてコールしようとしているので、インライン警告が出てコンパイルができなくなっている

　どうしてもこのメソッドを再コンパイルしたい場合は、`self xvtCheckForXvtSelector: encoder` を `self yourself xvtCheckForXvtSelector: encoder` などとすれば回避できます。もし何かミスをしてコンパイラ自体がうまく動かなくなってしまったら、環境を終了（ Squeak メニュー → `Quit` ）後、再起動して、またチャレンジしてみてください。

　次回起動後も機能するように、今回システムに加えた変更を永続化したければ、`Quit` ではなく、`Save and Quit` などでイメージを保存してから終了すればよいでしょう。