for VisualWorks 7.8 / 7.7 / 7.6
Smalltalkのプログラムをシステムブラウザを用いて作成してゆくことが多いと思います。そのプログラミング行為そのものをプログラムにしてみましょう。名付けて「プログラミングのプログラミング」です。パッケージを作るところから、名前空間を定義し、クラス群を生成し、メソッド群を作成し、例題プログラムをこしらえてテストを行い、ソースコードとしてセーブし、バイナリとしてセーブ(パーセル化)するところまで、これら一連のプログラムを紹介します。
Workbook.zipをダウンロードし、適当な場所に展開してください。Workbookというディレクトリができあがります。
$ curl -O http://www.cc.kyoto-su.ac.jp/~atsushi/Programs/VisualWorks/ProgrammingOfProgramming/Workbook.zip % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 6361 100 6361 0 0 58194 0 --:--:-- --:--:-- --:--:-- 94940 $ unzip Workbook.zip Archive: Workbook.zip creating: Workbook/ inflating: Workbook/Workbook.st creating: Workbook/Workspaces/ inflating: Workbook/Workspaces/1_Package.st inflating: Workbook/Workspaces/2_NameSpace.st inflating: Workbook/Workspaces/3_Classes.st inflating: Workbook/Workspaces/4_Methods.st inflating: Workbook/Workspaces/5_Methods.st inflating: Workbook/Workspaces/6_Methods.st inflating: Workbook/Workspaces/7_Testing.st inflating: Workbook/Workspaces/8_Saving.st inflating: Workbook/Workspaces/9_Parcel.st
そうしたら、VisualWorksを立ち上げ、ファイルブラウザを開き、Workbookのディレクトリの中を見てください。その中にWorkbook.stというファイルがありますので、それを選択してファイルイン(File In)します。
|
1_Packageから9_Parcelまでのタブを持つワークブックが現れます。パッケージを作るところから、名前空間を定義し、クラス群を生成し、メソッド群を作成し、テストを行い、ソースコードとしてセーブし、バイナリとしてセーブ(パーセル化)するところまで、スモールトーカー(Smalltalker)が暗記している(そらんじている)典型的なイディオム(慣用句としてのプログラム断片)を収録しています。
|
参考のため、上記のワークブックを出現させるプログラムを以下に紹介しておきます。(実のところ、下記のプログラムにもメタプログラミングが施されており、プログラミングのプログラミングになっています。ちょっと難しいので、どうぞ飛び越して、次へと歩を進めてくださいませ。)
| aDirectory aContext aBoolean aBlock aDialog aCollection aWorkbook aBuilder aRectangle | aDirectory := nil. aContext := thisContext. aBoolean := true. aBlock := [[aContext notNil and: [aBoolean yourself]] whileTrue: [(aContext selector == #fileIn and: [aContext receiver isKindOf: Stream]) ifTrue: [| aFilename | aFilename := aContext receiver stream fileName. aDirectory := aFilename head asFilename. aBoolean := false]. aContext := aContext sender]]. [aBlock value] on: aBlock messageNotUnderstoodSignal do: [:exception | aDirectory := nil. exception return]. aDirectory ifNil: [aDirectory := Filename defaultDirectory construct: 'Workbook'. aDirectory exists ifFalse: [aDialog := ChooseDirectoryDialog new. aDialog windowTitle: 'Choose the direcotry ' , aDirectory tail printString. aDialog open ifFalse: [^nil]. aDirectory := aDialog selection]]. aDirectory := aDirectory construct: 'Workspaces'. aDirectory exists ifFalse: [^nil]. aCollection := OrderedCollection new. aDirectory directoryContents asSortedCollection do: [:each | ('*.st' match: each) ifTrue: [aCollection add: (aDirectory construct: each)]]. aWorkbook := Workbook new. aCollection with: (1 to: aCollection size) do: [:each :index | | aStream pageContents labelString aPage | aStream := each readStream. [pageContents := aStream contents] ensure: [aStream close]. labelString := each tail asString. labelString := (Filename splitExtension: labelString) first. aPage := WorkspacePage labeled: labelString with: pageContents. index = 1 ifTrue: [aWorkbook replaceAllTextPagesWith: aPage] ifFalse: [aWorkbook addPage: aPage]]. aBuilder := aWorkbook allButOpenInterface: #windowSpec. aRectangle := (Screen default bounds center extent: 0 @ 0) expandedBy: 400 @ 250. aBuilder window openIn: aRectangle. aBuilder window displayPendingInvalidation. aWorkbook postOpenWith: aBuilder. ^aWorkbook
まず、システムブラウザにたよらずに、プログラムでパッケージを作るところから始めましょう。1_Packageのタブをひいて得られるプログラムを全選択して実行(Do it)してください。
|
実行したプログラムを示しておきます。Store.RegistryをレシーバにしてpackageNamedOrCreate: packageNameStringというメッセージを送っているところがミソです。
| packageNameString commentString aPackage | packageNameString := 'Forest-ProjectM'. commentString := 'Smalltalk Studies in Kyoto'. aPackage := Store.Registry packageNamedOrCreate: packageNameString. aPackage comment: commentString. ^aPackage
本当にパッケージができているのかを確かめましょう。システムブラウザを開けて調べればいいのですが、そこはプログラミングのプログラミング、プログラムでシステムブラウザを開け、当該のパッケージを選択してみます。以下のようなプログラムになります。
| packageNameString aPackage aBrowser aNavigator | packageNameString := 'Forest-ProjectM'. aPackage := Store.Registry packageNamed: packageNameString. aPackage ifNil: [^nil]. aBrowser := Refactory.Browser.RefactoringBrowser open. aNavigator := aBrowser navigator. aNavigator selectPundle: aPackage. ^aBrowser
実行しましょう。ただし、上記のプログラムを音読して(声に出して読んで)からにしてください。(なぜ、OrCreateのキャメルがないのか、おわかりいただけますよね。リファクタリングブラウザの開き方とパッケージの選択の仕方など、後ほど時間があるときにでも、RefactoringBrowserとBrowserNavigatorの2つのクラスを調査していただけると助かります。)
|
以下ではプログラム群と画像群に物を言わせて多くを語りませんが、どのメッセージ式がミソであるのか等々、メタプログラミングの高みをめざして、私たちのプログラミング行為の背後で動いているプログラムの外在化(externalization)に心を砕いていただければ幸いです。
次は名前空間です。2_NameSpaceのタブをひいて得られるプログラムを全選択して実行してください。
|
| nameSpaceSymbol packageNameString commentString aNameSpace aPackage | nameSpaceSymbol := #ProjectM. packageNameString := 'Forest-ProjectM'. commentString := 'Smalltalk Studies in Kyoto'. aPackage := Store.Registry packageNamed: packageNameString. aPackage ifNil: [^nil]. aNameSpace := Smalltalk at: nameSpaceSymbol ifAbsent: [nil]. aNameSpace ifNil: [aNameSpace := Smalltalk defineNameSpace: nameSpaceSymbol private: false imports: 'private Smalltalk.*' category: packageNameString. aNameSpace comment: commentString. aNameSpace yourself]. ([aNameSpace package] on: aNameSpace messageNotUnderstoodSignal do: [:exception | Store.Registry containingPackageForNameSpace: aNameSpace]) = aPackage ifFalse: [Store.XChangeSet current moveWholeObject: aNameSpace toPackage: aPackage]. ^aNameSpace
名前空間ができているのかを確かめます。
| nameSpaceSymbol aNameSpace aBrowser aNavigator aBinding | nameSpaceSymbol := #ProjectM. aNameSpace := Smalltalk at: nameSpaceSymbol ifAbsent: [^nil]. aBrowser := Refactory.Browser.RefactoringBrowser open. aNavigator := aBrowser navigator. aBinding := aNameSpace fullyQualifiedReference binding. aNavigator setBinding: aBinding. ^aBrowser
|
さぁ、簡単なMVC(モデル・ビュー・コントローラ)のためのクラス群(Model・View・Controller)を作成します。3_Classesのタブをひいて得られるプログラムを全選択して実行してください。
|
| nameSpaceSymbol packageNameString commentString aPackage aNameSpace aCollection | nameSpaceSymbol := #ProjectM. packageNameString := 'Forest-ProjectM'. commentString := 'Smalltalk Studies in Kyoto'. aPackage := Store.Registry packageNamed: packageNameString. aPackage ifNil: [^nil]. aNameSpace := Smalltalk at: nameSpaceSymbol ifAbsent: [^nil]. aCollection := (OrderedCollection new) add: (Array with: #Model with: #{UI.Model} with: 'forest picture'); add: (Array with: #View with: #{UI.View} with: 'offset'); add: (Array with: #Controller with: #{UI.Controller} with: ''); yourself. ^aCollection collect: [:anArray | | aClass | aClass := aNameSpace defineClass: (anArray at: 1) superclass: (anArray at: 2) indexedType: #none private: false instanceVariableNames: (anArray at: 3) classInstanceVariableNames: '' imports: '' category: packageNameString. aPackage addClass: aClass. aClass comment: commentString. aClass yourself]
ちゃんとクラス群が定義できたのかを確かめます。
| aCollection aBrowser aNavigator | aCollection := Array with: ProjectM.Model with: ProjectM.View with: ProjectM.Controller. aBrowser := Refactory.Browser.RefactoringBrowser open. aNavigator := aBrowser navigator. aNavigator state classesAndNameSpaces: aCollection. aNavigator setState: aNavigator state; changed. ^aBrowser
|
モデルのメソッド群を作ります。4_Methodsのタブをひいて得られるプログラムを全選択して実行してください。
|
| aClass aCollection | aClass := ProjectM.Model. aCollection := (OrderedCollection new) add: (Array with: 'accessing' with: ' picture ^picture '); add: (Array with: 'accessing' with: ' picture: anImage picture := anImage '); yourself. ^aCollection collect: [:anArray | | aProtocol aCode aTree aSelector aMethod | aProtocol := (anArray at: 1) asSymbol. aCode := (anArray at: 2) yourself. aTree := Refactory.Browser.RBParser parseMethod: aCode. aSelector := aClass compile: aTree formattedCode classified: aProtocol. aMethod := aClass compiledMethodAt: aSelector. aMethod yourself]
モデルのインスタンスメソッド群が作られているのかを確かめます。
| aClass aBrowser aNavigator | aClass := ProjectM.Model. aBrowser := Refactory.Browser.RefactoringBrowser open. aNavigator := aBrowser navigator. aNavigator selectClass: aClass. (aNavigator state) protocols: aClass organization categories; selectors: aClass selectors. aNavigator setState: aNavigator state; changed. ^aBrowser
|
ビューのメソッド群を作ります。5_Methodsのタブをひいて得られるプログラムを全選択して実行してください。
|
| aClass aCollection | aClass := ProjectM.View. aCollection := (OrderedCollection new) add: (Array with: 'controller accessing' with: ' defaultControllerClass ^Controller '); add: (Array with: 'displaying' with: ' displayOn: aGraphicsContext aGraphicsContext paint: ColorValue white; displayRectangle: self bounds. model ifNotNil: [model picture ifNotNil: [:it | it displayOn: aGraphicsContext at: offset]] '); add: (Array with: 'initialize-release' with: ' initialize super initialize. offset := 0 @ 0 '); add: (Array with: 'scrolling' with: ' scrollBy: aPoint self scrollTo: offset + aPoint '); add: (Array with: 'scrolling' with: ' scrollTo: aPoint offset := aPoint '); add: (Array with: 'updating' with: ' update: aNode self invalidateNow '); yourself. ^aCollection collect: [:anArray | | aProtocol aCode aTree aSelector aMethod | aProtocol := (anArray at: 1) asSymbol. aCode := (anArray at: 2) yourself. aTree := Refactory.Browser.RBParser parseMethod: aCode. aSelector := aClass compile: aTree formattedCode classified: aProtocol. aMethod := aClass compiledMethodAt: aSelector. aMethod yourself]
ビューのインスタンスメソッド群が作られているのかを確かめます。
| aClass aBrowser aNavigator | aClass := ProjectM.View. aBrowser := Refactory.Browser.RefactoringBrowser open. aNavigator := aBrowser navigator. aNavigator selectClass: aClass. (aNavigator state) protocols: aClass organization categories; selectors: aClass selectors. aNavigator setState: aNavigator state; changed. ^aBrowser
|
コントローラのメソッド群を作ります。6_Methodsのタブをひいて得られるプログラムを全選択して実行してください。
|
| aClass aCollection | aClass := ProjectM.Controller. aCollection := (OrderedCollection new) add: (Array with: 'events' with: ' blueButtonPressedEvent: event self mouseButtonActivity. ^nil '); add: (Array with: 'activities' with: ' clickActivity Transcript cr; show: thisContext printString '); add: (Array with: 'activities' with: ' grabActivity | mouse previous current | mouse := self sensor. previous := current := mouse cursorPoint. [mouse anyButtonPressed] whileTrue: [current := mouse cursorPoint. previous = current ifFalse: [(self view) scrollBy: current - previous; invalidateNow. previous := current]. Processor yield] '); add: (Array with: 'activities' with: ' mouseButtonActivity Cursor crossHair showWhile: [| symbol | symbol := self mouseButtonChecking. symbol = #click ifTrue: [self clickActivity]. symbol = #grab ifTrue: [self grabActivity]]. self sensor waitNoButton '); add: (Array with: 'activities' with: ' mouseButtonChecking | mouse limit | mouse := self sensor. limit := Time millisecondClockValue + 333. [Time millisecondClockValue < limit] whileTrue: [mouse anyButtonPressed ifFalse: [^#click]. Processor yield]. ^#grab '); add: (Array with: 'events' with: ' redButtonPressedEvent: event self mouseButtonActivity. ^nil '); add: (Array with: 'events' with: ' yellowButtonPressedEvent: event self mouseButtonActivity. ^nil '); yourself. ^aCollection collect: [:anArray | | aProtocol aCode aTree aSelector aMethod | aProtocol := (anArray at: 1) asSymbol. aCode := (anArray at: 2) yourself. aTree := Refactory.Browser.RBParser parseMethod: aCode. aSelector := aClass compile: aTree formattedCode classified: aProtocol. aMethod := aClass compiledMethodAt: aSelector. aMethod yourself]
コントローラのインスタンスメソッド群が作られているのかを確かめます。
| aClass aBrowser aNavigator | aClass := ProjectM.Controller. aBrowser := Refactory.Browser.RefactoringBrowser open. aNavigator := aBrowser navigator. aNavigator selectClass: aClass. (aNavigator state) protocols: aClass organization categories; selectors: aClass selectors. aNavigator setState: aNavigator state; changed. ^aBrowser
|
例題プログラムをモデルのクラスメソッドとして作成し、それを走らせて、テストします。7_Testingのタブをひいて得られるプログラムを全選択して実行してください。
|
| aClass aProtocol aCode | aClass := ProjectM.Model class. aProtocol := 'examples' asSymbol. aCode := ' example "ProjectM.Model example." | aModel aView aWindow aRectangle | aModel := Model new. aModel picture: (CachedImage on: (Screen default completeContentsOfArea: (Point zero extent: (Screen default bounds extent min: 1024 @ 768)))). aView := View model: aModel. aWindow := ApplicationWindow model: aModel label: nil minimumSize: 400 @ 300. aWindow component: aView. aRectangle := Point zero extent: 800 @ 600. aRectangle := aRectangle align: aRectangle center with: Screen default bounds center. aWindow openIn: aRectangle. ^aModel '. aTree := Refactory.Browser.RBParser parseMethod: aCode. aClass compile: aTree formattedCode classified: aProtocol. ^aClass soleInstance example
次のようなウィンドウが開いてきます。
|
システムブラウザで例題プログラムを見てみましょう。
| aClass aProtocol aSelector aBrowser aNavigator | aClass := ProjectM.Model class. aProtocol := #examples. aSelector := #example. aBrowser := Refactory.Browser.RefactoringBrowser open. aNavigator := aBrowser navigator. aNavigator selectClass: aClass. (aNavigator state) protocol: aProtocol; selector: aSelector. aNavigator setState: aNavigator state; changed. ^aBrowser
|
作ってきたものをソースコードとして(XML形式で)ファイルにセーブにします。8_Savingのタブをひいて得られるプログラムを全選択して実行してください。
|
| packageNameString aPackage aFilename | packageNameString := 'Forest-ProjectM'. aPackage := Store.Registry packageNamed: packageNameString. aPackage ifNil: [^nil]. aFilename := Filename defaultDirectory construct: packageNameString , '.st'. aPackage fileOutOnFileNamed: aFilename. ^aPackage
ソースコードがセーブされているのかを確かめます。ファイルブラウザを開き、当該ファイルを選択するプログラムになります。
| packageNameString aPackage aFilename aFileBrowser | packageNameString := 'Forest-ProjectM'. aPackage := Store.Registry packageNamed: packageNameString. aPackage ifNil: [^nil]. aFilename := Filename defaultDirectory construct: packageNameString , '.st'. aFilename exists ifFalse: [^nil]. aFileBrowser := Tools.FileTools.FileBrowser new. aFileBrowser open. aFileBrowser selectFileNamed: aFilename. ^aFileBrowser
|
作ってきたものを小包(パーセル)にします。9_Parcelのタブをひいて得られるプログラムを全選択して実行してください。
|
| packageNameString nameSpaceSymbol commentString aPackage aNameSpace aParcel | packageNameString := 'Forest-ProjectM'. nameSpaceSymbol := #ProjectM. commentString := 'Smalltalk Studies in Kyoto'. aPackage := Store.Registry packageNamed: packageNameString. aPackage ifNil: [^nil]. aNameSpace := Smalltalk at: nameSpaceSymbol ifAbsent: [^nil]. aParcel := Parcel parcelNamed: packageNameString. aParcel isNil ifTrue: [aParcel := Parcel createParcelNamed: packageNameString. aParcel comment: commentString]. aParcel addNameSpace: aNameSpace. aPackage allClasses do: [:aClass | aParcel addEntiretyOfClass: aClass]. aParcel parcelOutOn: packageNameString , '.pcl' withSource: packageNameString , '.pst' hideOnLoad: false republish: false backup: false. ^aParcel
ちゃんとパーセルとしてセーブされて(.pclと.pstの2つのファイルができあがって)いるのかを確かめます。
| packageNameString aPackage aFilename aFileBrowser | packageNameString := 'Forest-ProjectM'. aPackage := Store.Registry packageNamed: packageNameString. aPackage ifNil: [^nil]. aFilename := Filename defaultDirectory construct: packageNameString , '.pcl'. aFilename exists ifFalse: [^nil]. aFileBrowser := Tools.FileTools.FileBrowser new. aFileBrowser open. aFileBrowser selectFileNamed: aFilename. ^aFileBrowser
|
for VisualWorks 7.8 / 7.7 / 7.6