for Squeak 4.4 on Mac OS X (macOS 10.14 Mojave or earlier) / Windows (Windows 7 or later)
for Squeak 5.2 on Mac OS X (macOS 10.14 Mojave or later) / Windows (Windows 7 or later)
Smalltalk(Squeak)は、オブジェクト指向プログラミング言語のルーツ(起源)であり、プログラムがメッセージ(オブジェクトにメッセージを送信すること)で成り立っています。それゆえに、メッセージの三つの形式を会得してしまえば、文法の勉強はお終い(お仕舞い)です。これほど文法が簡単なプログラミング言語は滅多にありません。
松任谷由実(荒井由実)さんが作詞作曲された「やさしさに包まれたなら」の中に『目にうつる全てのことはメッセージ』というフレーズがあります。言い得て妙です。オブジェクト指向プログラミングの極意を、実にうまく言い表していますので。さぁ!サクサクとSmalltalk(Squeak)の文法を、おきばりやす!!
まずは、Workspace(ワークスペース)などに、プログラムを書きます。
メニューバーの「Tools」から「Workspace」を選び、開いてくるウィンドウにプログラムを記します。
プログラムの文字列をドラッグで選択し、黄ボタンメニュー(右ボタンを押して出るメニュー)から「do it」を選ぶと実行できます。
| aNumber | aNumber := 3 + 4. Transcript cr; show: aNumber printString. ^aNumber
オブジェクトを作っているだけのプログラムなどは、実行しても結果が目に見えず、分からない場合があります。
その場合は「print it」のメニュー項目を選んで、結果を印字してみましょう。
また「inspect it」でオブジェクトをインスペクタで調べるのも便利です。
コメントは、プログラム中に書く注釈のことです。
プログラムを実行しても、コメントにした部分は処理されません。
コメントはダブルクォート「"」で囲みます。
| aNumber | "definition of temporary variable 'aNumber'" aNumber := 3 + 4. "aNumber binds a result of 3 + 4." Transcript cr; show: aNumber printString. "output a value of aNumber to Transcript" ^aNumber "answer a value of aNumber"
日本語環境を設定していないと、日本語を記述しても、文字化けしますので注意してください。
個人の環境に依存するので、本来は、たとえコメントでも、プログラム内に日本語を直書きするのは推奨しません。
定数(リテラル)は、文字どおり、定まっているオブジェクトです。
代表的なものを、以下にまとめています。
定数の種類 | 具体例 | 備考 |
---|---|---|
整数 | 123 |
数字をそのまま書きます。桁数に制限はありません。 |
実数 | 123.456 |
数字と少数点を書きます。 |
文字列 | 'I am a Smalltalker.' |
表現したい任意の長さの文字列をシングルクォート「' 」で囲みます。 |
文字 | $a |
表現したい一文字の前にドル「$ 」を付けます。 |
真 | true |
true がクラス「真(True )」の唯一のインスタンスです。たとえば「 10 > 1 」の結果はtrue です。 |
偽 | false |
false がクラス「偽(False )」の唯一のインスタンスです。たとえば「 10 < 1 」の結果はfalse です。 |
不定 | nil |
オブジェクトが決まらない場合、nil となります。 |
配列 | #() |
空の配列。 この中に、スペースで区切りながら、要素として定数を記述します。 |
#(10 20 30) |
第1要素が10 、第2要素が20 、第3要素が30 の配列。 |
|
#('abc' 123 123.45) |
第1要素が文字列(String )、第2要素が整数(SmallInteger )、第3要素が実数(SmallFloat64 )の配列。 |
対義語は「変数(ヴァリアブル:variable)」になります。
システムで、すでに実装されているものです。
Array
(配列),OrderedCollection
(順序がある集まり),Point
(点:二次元座標)など、種々様々なクラスがあります。
クラス名は、大文字で始まっています。
たとえば配列クラスを利用して、配列の定数を作成してみましょう。
| anArray | anArray := Array new: 5. Transcript clear. anArray at: 1 put: 'aaa'. anArray at: 3 put: 'bbb'. anArray at: 5 put: 'ccc'. Transcript cr; show: anArray printString. anArray at: 2 put: 'ddd'. anArray at: 4 put: 'eee'. Transcript cr; show: anArray printString. ^anArray
トランスクリプトの出力結果:
#('aaa' nil 'bbb' nil 'ccc') #('aaa' 'ddd' 'bbb' 'eee' 'ccc')
「Transcript cr; show: anArray printString.
」で、そのときの配列の状態が出力されています。
変数は、種々様々な値(オブジェクト)を記憶しておく(束縛しておく:つなぎとめておく)ためのものです。
つなぎとめて(束縛して)おかないと、廃品回収(ガベージコレクション)の対象になり、無くなって(消滅して)しまいます。
Smalltalkの変数名は、以下のようになっています。
英字で始まる英数字で表現されます。
日本語やアンダーバー「_
」などを含めることができますが、あまり推奨されていません。
変数名の長さの制限はありませんので、記憶しておくオブジェクトを的確に表す名前を付けてください。
長い変数名をイニシャルなどで短くすることもタブーです。
長い名前のまま使用するよう心がけてください。
2語以上の単語が連なる際には、単語の間の空白を除いて、それぞれの単語の最初の文字を大文字にする慣習になっています。
また、変数の種類によって、小文字で始めるものと大文字で始めるものが慣習によって定められています。
たとえば「ある一つの整数」を表す「an integer」は、冠詞anと名詞integerの間の空白を除去し、
くっつけるときに、integerのiを大文字にして、「anInteger
」となるという具合です。
また「順序がある集まり」というクラスを表す「ordered collection」も、形容詞(過去分詞)orderedと名詞collectionの間の空白を除去し、
くっつけるときに、orderedのoを大文字にし、さらに、collectionのcも大文字して、「OrderedCollection
」となります。
実は、クラスを表すクラス名も変数の一種(グローバル変数:広域変数)であり、大文字で始めることになっているからです。
テンポラリ変数は、一時変数とも呼ばれ、いっときだけ、値(オブジェクト)を記憶しておくのに使用します。
縦棒「|
」で変数列を囲って宣言します。
宣言する場所はプログラムの先頭です。
テンポラリ変数の名前は、小文字で始めます。| anInteger aPoint | "definition of two temporary variables 'anInteger' and 'aPoint'" "... program statements ..." anInteger := 123. aPoint := 100 @ 200. (OrderedCollection new) add: anInteger; add: aPoint; yourself. "... program statements ..."
グローバル変数は、広域変数とも呼ばれ、みんなが知っているオブジェクト(たとえば、クラスなど)を記憶しています。
宣言する必要はありません。
グローバル変数の名前は、大文字で始まっています。OrderedCollection yourself. Transcript yourself. Array yourself. PopUpMenu yourself. "... a lot of ..."
メッセージ(message)は、メッセージセレクタ(message selector)と0個以上の引数(argument)からなり、レシーバ(receiver)に送られます。
すべてのメッセージはセンダ(sender)にオブジェクト(値)を応答します。
郵便物のイメージで、考えるようにするとよいでしょう。
メッセージ (message) |
![]() |
手紙 |
---|---|---|
レシーバ (receiver) |
![]() |
受取人 |
センダ (sender) |
![]() |
差出人 = 受取人が手紙を受け取った後の返信先 |
代表的なメッセージは、以下の三種類です。
メッセージを オブジェクト指向分析するとこのようになります。
『単項メッセージ = メッセージセレクタ』
単項メッセージは、0個の引数を持つメッセージ、つまり、引数の無いメッセージです。
ですから、メッセージはメッセージセレクタだけから構成されることになります。● 整数
3
へreciprocal
というメッセージを送ることにより、整数3
の逆数「(1/3)
」を生成します。3 reciprocal
レシーバ メッセージ メッセージセレクタ 引数 応答値 3
reciprocal
reciprocal
なし
3の逆数、すなわち、三分の一
(1/3)● 整数
3
へprintString
というメッセージを送ることにより、整数3
を文字列にした「'3'
」を生成します。3 printString
レシーバ メッセージ メッセージセレクタ 引数 応答値 3
printString
printString
なし
3の文字列
'3'● 整数
3
へasPoint
というメッセージを送ることにより、x座標3
、y座標3
の座標オブジェクト「3@3
」を生成します。3 asPoint
レシーバ メッセージ メッセージセレクタ 引数 応答値 3
asPoint
asPoint
なし
x座標3、y座標3の座標オブジェクト
3@3『二項メッセージ = メッセージセレクタ + 引数1個だけ』
二項メッセージは、引数を1個だけ持つメッセージです。
メッセージセレクタは「+」のような特殊記号になります。●
3
に4
を加算します。3 + 4
レシーバ メッセージ メッセージセレクタ 引数 応答値 3
+ 4
+
4
加算結果
7● 整数
100
から、x座標100
、y座標200
の座標オブジェクト「100@200
」を生成します。100 @ 200
レシーバ メッセージ メッセージセレクタ 引数 応答値 100
@ 200
@
200
x座標=100,y座標=200の座標オブジェクト
100@200●
'abc'
と'def'
をつなげて、'abcdef'
という文字列にします。'abc' , 'def'
レシーバ メッセージ メッセージセレクタ 引数 応答値 'abc'
, 'def'
,
'def'
連結文字列
'abcdef'● レシーバと引数の大小比較をして、レシーバが引数より小さい場合は「
-1
」、等しい場合は「0
」、大きい場合は「1
」を応答します。3 <=> 43 <=> 33 <=> 2
レシーバ メッセージ メッセージセレクタ 引数 応答値 3
<=> 4
<=>
4
レシーバが引数よりも小さい
-13
<=> 3
<=>
3
レシーバが引数と等しい
03
<=> 2
<=>
2
レシーバが引数よりも大きい
1『キーワードメッセージ = メッセージセレクタ + 引数1個以上』
キーワードメッセージは、1個以上の引数を持つメッセージです。
引数の数と同じ数だけのコロン「:
」で終わるキーワードを列挙し、それぞれのキーワード間に対応する引数を分かち書きした形になっています。● 配列の3番目の要素である
30
がセンダに応答されます。#(10 20 30 40 50) at: 3
レシーバ メッセージ メッセージセレクタ 引数 応答値 #(10 20 30 40 50)
at: 3
at:
3
レシーバの3番目の要素
30● 配列の3番目の要素を
678
にします。後述するカスケード「;
」で、同じレシーバに異なるメッセージを畳み掛けるように送信しています。#(10 20 30 40 50) at: 3 put: 678; yourself
レシーバ メッセージ メッセージセレクタ 引数 応答値 #(10 20 30 40 50)
at: 3 put: 678
at:put:
3
と
678配列の3番目の要素に入れたもの
678#(10 20 30 40 50)
yourself
yourself
なし
あなた自身と言われたレシーバ
#(10 20 678 40 50)●
(100 @ 200)
が始点で、大きさ(300 @ 400)
(幅300
、高さ400
、終点が(400 @ 600)
)の矩形(長方形)を生成します。Rectangle origin: (100 @ 200) extent: (300 @ 400)
レシーバ メッセージ メッセージセレクタ 引数 応答値 Rectangle
origin: (100 @ 200) extent: (300 @ 400)
origin:extent:
(100 @ 200)
と
(300 @ 400)始点が100@200で
終点が400@600の
矩形(長方形)
100@200 corner: 400@600メッセージは左から順番に処理されます。
同じ優先順位(同種のメッセージ)ならば、メッセージはそのまま左から順番に処理されます。
(※たとえば「+
」も「*
」も共に二項メッセージで(同種のメッセージで)ある場合で解題します。)3 + 4 * 5==> 7 * 5 ==> 35この順番は、括弧を用いることによって変更することができます。
3 + (4 * 5)==> 3 + 20 ==> 23今度は、異種のメッセージの場合を解題します。以下の優先順位があります。
『単項メッセージ > 二項メッセージ > キーワードメッセージ』
つまり、いろいろな種類のメッセージを書いた場合、 以下のような順番で処理されることになります。
| anArray | anArray := Array new: 5. anArray at: 1 put: 3 asPoint + 4 asPoint. ^anArray==> anArray at: 1 put: 3 asPoint + 4 asPoint "まず、単項メッセージが処理されます" ==> anArray at: 1 put: (3 @ 3) + (4 @ 4) "次に、二項メッセージが処理されます" ==> anArray at: 1 put: (7 @ 7) "最後に、キーワードメッセージが処理されます"配列の1番目の要素が、x座標
7
、y座標7
の座標オブジェクトになります。ちょっと複雑な例ですが、以下のような順番になります。
理由はわかるでしょうか?Rectangle origin: 1 asPoint + 2 asPoint extent: 3 asPoint + 4 asPoint==> Rectangle origin: 1 asPoint + 2 asPoint extent: 3 asPoint + 4 asPoint "まず、第一引数の単項メッセージが処理されます" ==> Rectangle origin: (1 @ 1) + (2 @ 2) extent: 3 asPoint + 4 asPoint "次に、第一引数の二項メッセージが処理されます" ==> Rectangle origin: (3 @ 3) extent: 3 asPoint + 4 asPoint "そして、第二引数の単項メッセージが処理されます" ==> Rectangle origin: (3 @ 3) extent: (3 @ 3) + (4 @ 4) "それから、第二引数の二項メッセージが処理されます" ==> Rectangle origin: (3 @ 3) extent: (7 @ 7) "最後に、キーワードメッセージが処理されます"始点
(3 @ 3)
、大きさ(7 @ 7)
の矩形(3 @ 3 corner: 10 @ 10)
が生成されます。
変数にオブジェクトを束縛(バインディング:binding)する場合は、ゲット(コロンとイコール)「:=
」の左側に変数を書き、右側にメッセージ式を書きます。
データ型(タイプ)は存在しないので、どんなオブジェクトも束縛できます。
● テンポラリ変数「anInteger
」に、定数「123
」を束縛します。
そして「anInteger
」すなわち「123
」を応答します。
| anInteger | anInteger := 123. ^anInteger
● 配列「#(10 20 30 40 50)
」を テンポラリ変数「anArray
」に束縛します。
さらに「anArray
」の第3番目の要素である「30
」を「anInteger
」というテンポラリ変数に束縛します。
そして「anInteger
」すなわち「30
」を応答します。
| anArray anInteger | anArray := #(10 20 30 40 50). anInteger := anArray at: 3. ^anInteger
(※蛇足になりますが、代入と束縛はまったく異なります。さらには、統合というのもあります。)
カスケードは、レシーバを決めて、メッセージを畳み掛けるように送るのに便利です。
続けざまに送るメッセージの間に、セミコロン「;
」を挿入して記述します。
Transcript cr. Transcript show: 'I am a Smalltalker.'.
上記のプログラムは、レシーバが同じ「Transcript
」なので、カスケードを利用できて、下記のように書けます。
Transcript cr; show: 'I am a Smalltalker.'.
(※蛇足になりますが、カスケードを用いると実行速度が向上します。バイトコード列が短くなるのです。)
簡単にプログラムの実行を確認するには、トランスクリプトに文字を出力します。
● 「'
」で囲まれた文字列を出力します。
Transcript show: 'example start.'.
トランスクリプトの出力結果:
example start.
● あるオブジェクトを文字列にして出力します。
| anObject | anObject := #(10 20 30 40 50). Transcript show: anObject printString.
トランスクリプトの出力結果:
#(10 20 30 40 50)
● トランスクリプトに書かれている文字を全て消したり、改行やスペース、タブを出力します。
何かを出力する際には、分かりやすいように、これらを活用しましょう。
Transcript clear. "トランスクリプトに書かれている文字を全て消去" Transcript cr. "改行(Carriage Return)を出力" Transcript space. "スペースを出力" Transcript tab. "タブを出力" Transcript tab: 3. "タブを3個出力"
| anArray | anArray := #(10 20 30 40 50). Transcript clear. anArray doWithIndex: [:each :index | Transcript cr; tab; show: index printString; space; show: each printString]
トランスクリプトの出力結果:
1 10 2 20 3 30 4 40 5 50
(※蛇足になりますが、トランスクリプトはプログラムのテスト&デバッグをする際に重宝します。)
ブロッククロージャは、ブラケット[ ]
で囲んだSmalltalkプログラム(メッセージ)です。
● 「3 + 4
」のプログラムをブロッククロージャにします。
[3 + 4]
==> [closure] in UndefinedObject>>DoIt
ブロッククロージャのままでは、中に書かれているプログラムは実行されません。
ブロッククロージャ内に書かれているプログラムを実行するには、ブロッククロージャに対して「value
」のメッセージを送り(ブロッククロージャを評価し)ます。
[3 + 4] value
==> 7
レシーバ | メッセージ | メッセージセレクタ | 引数 | 応答値 |
---|---|---|---|---|
[3 + 4] |
value |
value |
なし |
ブロッククロージャの実行結果 |
● トランスクリプトに出力するプログラムをブロッククロージャにします。(引数なし)
ブロッククロージャをテンポラリ変数にとっておいて、後で評価しています。
トランスクリプトに書かれている文字が全て消えた後に、ブロッククロージャ内のプログラムが実行されていることを確認してください。
同じような処理をする場合に、ブロッククロージャを変数に取っておいて、実行するタイミングで評価すると便利です。
| aBlock | aBlock := [Transcript cr; show: '--- BlockClosure ---']. Transcript clear. aBlock value
トランスクリプトの出力結果:
--- BlockClosure ---
● トランスクリプトに出力するプログラムをブロッククロージャにします。(引数あり)
引数がある場合には、各引数の前にコロン「:
」を付け,最後の引数の後ろに縦棒「|
」を置きます。
このように定義された変数を、ブロック変数といいます。
ブロッククロージャを評価するときに、「value:
」で引数の値を渡します。
| aBlock | aBlock := [:object | Transcript cr; show: '-----'; space; show: object printString; space; show: '-----']. Transcript clear. aBlock value: 123. aBlock value: 100 @ 200. aBlock value: #(10 20 30). aBlock value: $A. aBlock value: 'Smalltalk'
トランスクリプトの出力結果:
----- 123 ----- ----- 100@200 ----- ----- #(10 20 30) ----- ----- $A ----- ----- 'Smalltalk' -----
● トランスクリプトに出力するプログラムをブロッククロージャにします。(複数の引数あり)
複数の引数がある場合は、評価する際に、その個数分「value:
」を続けます。(※この方法では引数3個まで)
また、ブロッククロージャの中でも、ブロッククロージャ内の先頭にテンポラリ変数を縦棒「|
」で囲って定義することができます。
そのブロッククロージャの内部だけで有効なテンポラリ変数になります。
| aBlock | aBlock := [:object :character :size | | aStream aString | aStream := String new writeStream. [aStream nextPutAll: (String new: size withAll: character); space; nextPutAll: object printString; space; nextPutAll: (String new: size withAll: character). aString := aStream contents] ensure: [aStream close]. Transcript cr; show: aString size printString; space; show: aString]. Transcript clear. aBlock value: 123 value: $+ value: 10. aBlock value: 100 @ 200 value: $- value: 11. aBlock value: #(10 20 30) value: $* value: 12. aBlock value: $A value: $/ value: 13. aBlock value: 'Smalltalk' value: $. value: 14
トランスクリプトの出力結果:
25 ++++++++++ 123 ++++++++++ 31 ----------- 100@200 ----------- 37 ************ #(10 20 30) ************ 30 ///////////// $A ///////////// 41 .............. 'Smalltalk' ..............
(※少々難しい事柄を述べますね。ブロッククロージャは、関数プログラミングでいうところのラムダやクロージャに相当します。)
(※名前の無いメソッドとか、匿名の関数とか、いろいろな把握の仕方がありますが、プログラムをオブジェクト化したものになります。)
(※実のところ、ブロッククロージャは、リテラルの一種でして、処理自体を定数オブジェクトにし、変数や引数に束縛して扱うことを可能にします。)
(※蛇足になりますが、ブロッククロージャを駆使できるようになると、オブジェクト指向プログラミングと関数プログラミングの免許皆伝です。)
条件によって、処理を分けます。
● ダイアログで選んだボタンによって、トランスクリプトに出力される文字が変わります。
(Object confirm: 'Are you happy?') ifTrue: [Transcript cr; show: 'I am happy.'] ifFalse: [Transcript cr; show: 'I am not happy.']
レシーバ | メッセージ | メッセージセレクタ | 引数 | 応答値 |
---|---|---|---|---|
(Object confirm: 'Are you happy?') |
ifTrue: [Transcript cr; show: 'I am happy.'] |
ifTrue:ifFalse: |
[Transcript cr; show: 'I am happy.'] |
実行したブロッククロージャの中の |
● 選んだメニュー項目によって、カーソル(マウスの形)を変化させます。
| aMenu anInteger aCursor | aMenu := PopUpMenu labelArray: #('crossHair' 'wait' 'webLink'). anInteger := aMenu startUp. aCursor := nil. anInteger = 1 ifTrue: [aCursor := Cursor crossHair]. anInteger = 2 ifTrue: [aCursor := Cursor wait]. anInteger = 3 ifTrue: [aCursor := Cursor webLink]. aCursor ifNotNil: [aCursor showWhile: [(Delay forSeconds: 3) wait]]
一見、C言語などで使われるif
文のように見えますが、文ではなくて、メッセージなのです。
実は、真偽(Boolean
のインスタンス)のtrue
やfalse
がifTrue:
やifTrue:ifFalse:
などのメッセージセレクタを受け取って処理を行っています。
引数で指定されているブロッククロージャを、true
やfalse
が評価(ブロッククロージャにvalue
のメッセージを送信)しているのです。
true ifTrue: [Transcript cr; show: (3 + 4) printString]
そして、すべてのオブジェクトが受信できるifNotNil:
も同様でして、レシーバのオブジェクトがnil
ではないときに、
メッセージの引数として渡って来るブロッククロージャにvalue
のメッセージを送信しています。
123 ifNotNil: [Transcript cr; show: (3 + 4) printString]
(※蛇足になりますが、オブジェクト指向プログラミングにおいて、条件分岐もメッセージなのです。)
同じ処理を繰り返します。
● 10
回、同じことを繰り返します。
10 timesRepeat: [Transcript cr; show: 'I am a Smalltalker.']
レシーバ | メッセージ | メッセージセレクタ | 引数 | 応答値 |
---|---|---|---|---|
10 |
timesRepeat: [Transcript cr; show: 'I am a Smalltalker.'] |
timesRepeat: |
[Transcript cr; show: 'I am a Smalltalker.'] |
レシーバ |
● 1
から10
までの数値をトランスクリプトに出力します。
(1 to: 10) do: [:number | Transcript cr; show: number printString]
● 10
回、1
を足した結果を返します。
| total | total := 0. 10 timesRepeat: [total := total + 1]. ^total
==> 10
● 1
から10
までの数値を足した結果を返します。
| total | total := 0. (1 to: 10) do: [:number | total := total + number]. ^total
==> 55
(※蛇足になりますが、オブジェクト指向プログラミングにおいて、繰り返しもメッセージなのです。)
Interval
(間隔)は、始まりと終わり、刻みを指定します。
● 1
から始まって10
までの刻みが1
である間隔を作成します。
1 to: 10
レシーバ | メッセージ | メッセージセレクタ | 引数 | 応答値 |
---|---|---|---|---|
1 |
to: 10 |
to: |
10 |
間隔 |
● 0
から始まって1
までの刻みが0.2
である間隔を作成し、配列に変換します。
(0 to: 1 by: 0.2) asArray
==> #(0.0 0.2 0.4 0.6 0.8 1.0)
● 100
から始まって-100
までの刻みが-3
である間隔を作成し、間隔に含まれる要素の個数を答えます。
(100 to: -100 by: -3) size
==> 67
(※蛇足になりますが、間隔が集まりの一種として重要な役割を演じており、列挙の手助けしています。)
OrderedCollection
(順序がある集まり)は、いろいろなオブジェクトをひとつの集まりとして扱います。
● (0 @ 0)
,(0 @ 1)
,(1 @ 0)
,(1 @ 1)
の4つ座標を集まりに追加し、トランスクリプトに出力します。
| aCollection | aCollection := OrderedCollection new. aCollection add: (0 @ 0). aCollection add: (0 @ 1). aCollection add: (1 @ 0). aCollection add: (1 @ 1). aCollection do: [:aPoint | Transcript cr; show: aPoint printString]. ^aCollection
● 1
から100
までの奇数だけを集まりに追加し、トランスクリプトに出力します。
| aCollection | aCollection := OrderedCollection new. (1 to: 100 by: 2) do: [:aNumber | aCollection add: aNumber]. aCollection do: [:aPoint | Transcript cr; show: aPoint printString]. ^aCollection
(※蛇足になりますが、順序がある集まりは、配列やリストなどよりも頻出する(扱いやすい)と言っても過言ではありません。)