前回、いろんな変な処理系で書き散らかしてしまったのを反省したばかりだったのですが…。つい、また。w
f の定義を記述してから、同じような g を、しかし、今度はステップ数を出力するように再び記述しなおすのは何か違うよな…と感じたとき、ふと思い出したのがアスペクト指向です。もっとも、必ず 1 に到達すると予想されている f を、素直な記述のまま手を加えずに残しておくことには、その意味をほとんど見いだせないので、単純に、いい機会だからアスペクト指向のまねごとをしてみたかった…というだけのはなしだとも(^_^;)。
AspectS は、ロバート・ハーシュフェルドが、XEROX パロアルト研究所(PARC)の AspectJ と、ジョン・ブラントの MethodWrappers のアイデアを組み合わせ、Squeak の Smalltalk を拡張することで、実験的なアスペクト指向プログラミング環境として構築したものです。残念ながら、公開用ページは 2005 年ごろに消滅してしまったようですが、web.archive.org で見つけることができました。半ばリファレンス代わりの論文は、web.archive.org のキャッシュからも、他からも入手可能です。
サイト:
- AspectS(archive.org)
文献:
- AspectS -- Aspect-Oriented Programming with Squeak (PDF - archive.org)
比較的最近公開されたバージョンは Squeak 3.7 向けのもので、SqueakMap のエントリーにはあるのですが、Squeak 3.7 が最新の SqueakMap クライアント(SqueakMap Package Loader)に対応していないため、Squeak 3.7 環境内での SqueakMap Package Loader を経由したインストールはできません。パッケージ紹介ページの最後のリンクからチェンジセットファイル(.cs)をダウンロードして、Squeak 3.7 の仮想イメージと同じディレクトリに置いてから、あらためて環境内からファイルイン(たとえば、デスクトップメニュー → open... → file list → .cs 選択 → install ボタン)してください。
インストールが済んで簡単なチュートリアルが記述したウインドウが開いたら、ファイルリストを閉じ、デスクトップメニュー → save as... → AspectS.image → Accept などとして、環境(仮想イメージ)に名前を付けて保存しておくことをお薦めします。
さて、AspectS の準備ができたところで、本題の Collatz 予想です。まず、お題の f をメソッド #f: として定義します。どこでもいいのですが、Collatz というクラスを作り、そのクラスメソッド「Collatz class >> #f:」として定義しました。ファイルイン可能な書式で定義を示します。
Object subclass: #Collatz instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'Category-doukaku2'! !Collatz class methodsFor: 'functions' stamp: 'sumim 7/13/2006 21:27'! f: n n = 1 ifTrue: [^ 1]. n even ifTrue: [^ Collatz f: n/2]. n odd ifTrue: [^ Collatz f: 3*n + 1]! !
次のようにして使えますが、当然のことながらいつも 1 しか返しません。
Collatz f: 3 " => 1 "
そこで、アスペクト指向です。Collatz class >> #f: の起動に関連付けたアスペクトをクラス「AsCollatz」として定義します。
AsAspect subclass: #AsCollatz instanceVariableNames: 'steps upto ' classVariableNames: '' poolDictionaries: '' category: 'Category-doukaku2'! !AsCollatz methodsFor: 'advice' stamp: 'sumim 7/13/2006 21:44'! adviceAllButFirst ^ AsBeforeAfterAdvice qualifier: (AsAdviceQualifier attributes: #(receiverClassSpecific cfAllButFirstInstance)) pointcut: [OrderedCollection with: ( AsJoinPointDescriptor targetClass: Collatz class targetSelector: #f:)] beforeBlock: [:receiver :arguments :aspect :client | steps := steps + 1. upto := upto max: arguments first]! ! !AsCollatz methodsFor: 'advice' stamp: 'sumim 7/13/2006 21:43'! adviceFirst ^ AsBeforeAfterAdvice qualifier: (AsAdviceQualifier attributes: #(receiverClassSpecific cfFirstInstance)) pointcut: [OrderedCollection with: ( AsJoinPointDescriptor targetClass: Collatz class targetSelector: #f:)] beforeBlock: [:receiver :arguments :aspect :client | steps := 1. upto := arguments first]! ! !AsCollatz methodsFor: 'accessing' stamp: 'sumim 7/13/2006 21:35'! steps ^ steps! ! !AsCollatz methodsFor: 'accessing' stamp: 'sumim 7/13/2006 21:42'! upto ^ upto! !
いろいろな小難しい記述は私もよくわかっていないので(^_^;)、細かいところは上のリンク先にある論文を読んでいただくことにして、ここでのポイントは、#advice〜 という二つのメソッドが返す、an AsBeforeAfterAdvice 生成時の beforeBlock: キーワードのパラメータとして、ステップ数と最大到達値を記録するためのコード断片が定義されていることです。
#adviceFirst は、Collatz class >> #f: が最初に起動されたときにジョインポイントに挿入されるコード断片オブジェクトを返します。ステップ数カウンタ(steps)と最大到達値のメモ(upto)の初期化を行なっています。
#adviceAllButFirst は、同じく Collatz class >> #f: が、今度は再帰的に起動されたときに挿入されるコード断片です。ステップ数カウンタのカウントアップと最大到達値の更新を行なっています。
アスペクトは次のように、メッセージ「install」の送信で活性化され、しかるべくタイミングで先に定義したコード断片が挿入され、実行されます。
| aspect | aspect := AsCollatz new. aspect install. Collatz f: 3. aspect uninstall. ^ aspect steps -> aspect upto
=> 8 -> 16
当然ですが、Collatz class >> #f: にはいっさい手を加えずに、適宜、しかるべきコード断片を挿入して挙動を変更できているのがすごいところです。
お題にしたがって、ステップ数を求める g としてメソッド「AsCollatz class >> #g:」を、範囲内で最大のステップ数を持つ自然数を返す h を「AsCollatz class >> #h:」として定義しましょう。
!AsCollatz class methodsFor: 'functions' stamp: 'sumim 7/13/2006 22:33'! g: n | aspect | aspect := self new. aspect install. Collatz f: n. aspect uninstall. ^ aspect steps -> aspect upto! ! !AsCollatz class methodsFor: 'functions' stamp: 'sumim 7/13/2006 22:35'! h: n ^ (1 to: n) inject: 0->0->0 into: [:assoc :k | assoc max: (AsCollatz g: k) -> k]! !
では、最後に、お題の h(100) を求めます。
AsCollatz h: 100 " => 119 -> 9232 -> 97 "