Smalltalk基礎文法最速マスター(Squeak版)

流行りには乗っておくのが吉…と思いつつ、なかなか書き進められないので、とりあえず、クラスやメソッド定義より前のごく基礎的な部分について。思いついたときに断わりなく追記・修正することがありますので、どうぞあしからず。(さっそくですが、タイトルを他の言語のものにあわせて変更しました)


■基本的な考え方

Smalltalk では「オブジェクトに対してメッセージを送る」という考え方のみでコードを書きます。「メッセージ式」と呼ばれる式のみを用います。


▼メッセージ式
メッセージ式の基本文法です。

receiver message


▼カスケード
式の終わりにセミコロン「;」を置くと、直前のメッセージのレシーバーに対して、続くメッセージを畳みかけるようにして送ることができます。これを「カスケード」と呼びます。

receiver message1; message2; message3


▼式の区切り
複数のメッセージ式を一挙評価する場合は、ピリオド「.」(と、通常は空白文字)でそれぞれの式を区切ります。

receiver1 message1. receiver2 message2. receiver3 message3


改行を入れてもピリオドは省略できません。

receiver1 message1.
receiver2 message2.
receiver3 message3


なお、最後の式の終端にピリオドがあっても無視されるので付けるのも付けないのも自由です。

receiver1 message1.
receiver2 message2.
receiver3 message3.

セレクタ

メッセージは、「セレクター」と0個以上の引数で構成されます。セレクターは、メッセージを送ることでコールされる「メソッドの名前」と考えると簡単です。

セレクターは引数の数だけコロン「:」を含みます。メッセージを構成する際には、セレクター途中の各コロンの直後に引数を挿入します。

3 between: 2 and: 4

たとえばこのメッセージ式であれば、3 がレシーバ、between: 2 and: 4 がメッセージ、between:and: がセレクター(つまり、メソッド名)、2 と 4 が引数になります。念のため、通常の言語での式の記法を用いて同じことを表現するとこうなります(ただしメソッド名にコロンを含められる言語の場合)。

3.between:and:(2,4)


セレクターには頭に # を付けて #between:and: などと表記することもあります。(以下でもそうしています)


▼キーワードメッセージ
セレクターをコロンで区切った要素を「キーワード」と呼ぶことがあります。#between:and: であれば、between: と and: がキーワードです。ただこの呼び名はセレクターの部分を呼称するための便宜的なもので、Pythonのキーワード引数のような特殊な仕組みがあるわけでも、メッセージ構成時にキーワードの順序を変えられたりするわけでもないことに注意してください。

なお、引数を1つ以上伴う、通常のメッセージのことを「キーワードメッセージ」と呼びます。


▼単項メッセージ
メッセージのうち、引数を伴わないものを「単項メッセージ」と呼びます。引数を伴わないのでメッセージとセレクター(コールするメソッド名)は一致します。

3 factorial


▼二項メッセージ
通常の言語における二項演算を模した、引数をひとつだけとるメッセージを「二項メッセージ」と呼びます。セレクターが1文字以上の記号のみの組み合わせからなる点、コロンで終わらない点が、引数1つのキーワードメッセージと異なるところです。

3 + 4

念のため、この 3 + 4 では、3 がレシーバー、+ 4 がメッセージ、#+ がセレクターで、4 が引数です。


▼優先順位
単項メッセージ > 二項メッセージ > キーワードメッセージの順で評価されます。二項メッセージ間には優先順の差はありません。たとえば、

7 + 6 * 5 max: 4 factorial

は、

((7 + 6) * 5) max: (4 factorial)

の順で、評価されます。


■コメント

コメントはダブルクオーテーションで括ります。改行を含めることもできます。ダブルクオーテーションを含める場合は、連続させます。

"コメント"

リテラル


▼数値リテラル

3            "整数"
3.4          "浮動小数点数"
3.4e5        "浮動小数点数"
5.400s3      "固定小数点数"
2r10101111   "N進数(Nは2以上35未満)"


リテラルではないがよく使う数値関連オブジェクトの生成式

3 / 4     "分数(整数にメッセージ 「/ 整数」 を送信"
3 @ 4     "座標(数値にメッセージ 「@ 数値」 を送信)"
4 i       "虚数(数値に単項メッセージ i を送信)"
3 + 4 i   "複素数(実数と虚数の和の式)"


▼数値以外のリテラル

'str'      "文字列"
#sym       "シンボル(同値なら同一性を保証される不可変文字列)"
$a         "文字"
#(1 2 3)   "配列(区切りはカンマではなくスペース)"
#('this' #is $a 10)   "配列(異種オブジェクトを混在できる。ただし要素はリテラルのみ)"


配列リテラルの要素となるシンボルおよび配列は # を省略できます。

#(sym1 sym2 ('this' is $a 10)) == #(#sym1 #sym2 #('this' #is $a 10))


▼未定義値、真偽値

nil     "未定義値"
true    "真"
false   "偽"

nil true false はリテラルではなく、擬変数(値の参照はできるが代入はできない変数)です。それぞれ、UndefinedObject True False というクラスのソルインスタンス(唯一のインスタンス)が代入されています。

nil true false はリテラルではないので、本来、配列リテラルの要素に含めることはできないはずなのですが、最近のバージョンの Squeak ではこれができるようになってしまっています。

#(nil true false)   "=> 古いバージョンの Squeak ではシンボルになってしまったが、今は許される "


Squeak Smalltalk では連続した複数の式を { } で括ることで、それぞれの式の評価結果を要素とした配列を動的に生成できます。この場合、要素の区切りに相当するのはスペース(や、通常の言語のようにカンマ)ではなくピリオドであることに注意しましょう。

#(3+4 3-4 3*4)    "=> #(3 #+ 4 3 -4 3 #* 4) …… 式のつもりでも、シンボルなどに解釈されてしまう "
{3+4. 3-4. 3*4}   "=> #(7 -1 12) "

■配列

▼要素へのアクセス
インデックスによる要素の参照には #at: を、代入には #at:put: を使います。なお、Smalltalk の配列を含む、順序付きコレクションの要素のインデックスは、0からではなく1から始めます。

| array |
array := #(a b c).
array at: 2.          "=> #b "
array at: 2 put: #X.
array                 "=> #(#a #X #c) "


▼配列の結合
配列同士を結合して新しい配列を作るには #, を使います。

#(a b c), #(d e)   "=> #(#a #b #c #d #e) "


▼配列への要素の追加や削除
Smalltalk では配列への要素の追加や削除はできません。要素の追加や削除が必要なときは、要素をコピーして新しい配列を作るか、OrderedCollection のインスタンスを使います。

| array1 array2 |
array1 := #(a b c).
array2 := array1 allButFirst.   "=> #(#b #c) "
{array1. array2}   "=> #(#(#a #b #c) #(#b #c)) "
| coln1 coln2 |
coln1 := #(a b c) asOrderedCollection.
coln2 := coln1.
coln1 removeFirst.
{coln1 asArray. coln2 asArray}   "=> #(#(#b #c) #(#b #c)) "

■辞書

他の言語でのハッシュは「辞書(dictionary)」と呼ばれ、Dictionary クラスのインスタンスです。キーに対する値のアクセスには #at: #at:put: を使います。リテラルはないので通常は空の Dictionary のインスタンスに #at:put: して手続き的に作ります。

| dict |
dict := Dictionary new.
dict at: #a put: 1.
dict at: #b put: 2.
dict at: #c put: 3.
dict   "=> a Dictionary(#a->1 #b->2 #c->3 ) "


カスケードも使えます。

| dict |
dict := Dictionary new.
dict at: #a put: 1; at: #b put: 2; at: #c put: 3.
dict "=> a Dictionary(#a->1 #b->2 #c->3 ) "


キーと値の対で構成されるアソシエーション(Association クラスのインスタンス。#-> で生成可)を要素に持つ配列に対して as: Dictionary することで、リテラルっぽく生成することも可能です。

{#a->1. #b->2. #c->3} as: Dictionary   "=> a Dictionary(#a->1 #b->2 #c->3 ) "

■変数と代入

Smalltalk では、変数は原則として宣言してから用います。たとえばテンポラリ変数であれば、評価する式の並びの直前で、以降の式で使用するテンポラリ変数名をスペースで区切って列挙し、「|」で括ります。変数への代入は「:=」を使います。古いソースでは「_」を使っていることもあります。またその際、フォントの設定によってはグリフが「←」のように見える場合もあるので注意してください。

| temp1 temp2 |
temp1 := 3.
temp2 := 4.
temp1 + temp2


グローバル変数の宣言
グローバル変数は「Smalltalk」というグローバル変数に代入されている辞書(Dictionary のインスタンス)にキーを新たに登録することで宣言できます。

Smalltalk at: #Hoge put: nil


グローバル変数の削除
不要なグローバル変数を使用できなくするためには、グローバル変数Smalltalk」から、該当するキーを削除します。

Smalltalk removeKey: #Hoge

■ブロック

無名関数です。式を [ ] で括ることで作ることができます。評価するときはメッセージ value を送ります。

| block |
block := [3 + 4].
block value   "=> 7 "


ブロックを評価する際に引数を与えたいときは、ブロック定義時にブロック変数を宣言しておきます。ブロック変数の宣言は,ブロック記述の最初の [ の直後に コロン+ブロック変数名 をスペースで区切って列挙し、終端に「|」を添えます。

[:a :b | a + b]


ブロック変数を持つブロックをコールするときには、必要とされる引数の数(宣言されたブロック変数の数)に見合うセレクター #value: #value:value: #value:value:value: …を使い分けるか、#valueWithArguments: に引数を要素に持つ順序付きコレクションを引数として与えます。

| block |
block := [:a :b | a + b].
block value: 3 value: 4.           "=> 7 "
block valueWithArguments: #(3 4)   "=> 7 "


なお、多くの Smalltalk と違い、Squeak Smalltalk のブロックはクロージャーではないので、再帰・再入をするコードを書くときには注意してください(これは執筆時点の Squeak4.0 以前の話。Squeak4.1 以降の最近のバージョンではクロージャーになっています)

| result maker block1 block2 |
result := OrderedCollection new.
maker := [|v| v := 0. [result add: (v := v + 1)]].
block1 := maker value.
block2 := maker value.
5 timesRepeat: [block1 value. block2 value].
result asArray
"Squeak の場合      => #(1 2 3 4 5 6 7 8 9 10) "
"VisualWorks の場合 => #(1 1 2 2 3 3 4 4 5 5) "


ちなみに、ブロックの環境を #fixTemps で複製することにより、クロージャーライクな動きをさせることは可能です。

| result maker block1 block2 |
result := OrderedCollection new.
maker := [|v| v := 0. [result add: (v := v + 1)]].
block1 := maker value fixTemps.
block2 := maker value fixTemps.
5 timesRepeat: [block1 value. block2 value].
result asArray   "=> #(1 1 2 2 3 3 4 4 5 5) "


再帰・再入を模すためには、さらにブロック自体の複製も必要になります。

| fact |
fact := nil.
fact := [:n | n < 2 ifTrue: [n] ifFalse: [n * (fact copy fixTemps value: n-1)]].
fact copy fixTemps value: 10   "=> 3628800 "


■制御構造

Smalltalk では条件分岐やループなどの処理も、メッセージ式で記述します。条件ごとに評価される式は [ ]で括ってブロックにし、引数として渡します。

▼条件分岐

3 < 4 ifTrue: [5] ifFalse: [6]


念のため、3 < 4 の返値(この場合は true )がレシーバ、ifTure: [5] ifFalse: [6] がメッセージ、#ifTrue:ifFalse: がセレクターで、[5] と [6] が引数です。

ただ、Squeak Smalltalk をはじめ、たいていの Smalltalk 処理系では、この条件分岐式を含めよく使用される制御構造ライクな式は、コンパイル時にインライン展開され、バイトコードレベルでは条件付きジャンプなどに置き換えられてしまいます。したがって、#ifTrue:ifFalse: 等のメソッドは存在しますし強制的にコールして機能させることもできますが(3 < 4 perform: #ifTrue:ifFalse: withArguments: {[5]. [6]} "=> 5 ")、実際のコードでは ifTrue: [...] ifFalse: [...] のようなメッセージが送られたり、#ifTrue:ifFalse: がコールされることはないので、たとえば、学習が進み、#ifTrue:ifFalse: を再定義したくなる衝動にかられたときなどには注意が必要です。


▼ケース分岐

6 atRandom caseOf: {
   [1] -> [#one].
   [2] -> [#two].
   [3] -> [#three]
} otherwise: [#else]

▼条件付きループ

| count |
count := 0.
[count < 10] whileTrue: [count := count + 1]


終了条件を記したレシーバは、ブロックでなければならないことを注意してください。


▼回数指定ループ

10 timesRepeat: [#doSomething]


▼無限ループ

[#doSomething] repeat

alt + ピリオド(Mac では コマンド + ピリオド)で停止できます。


▼コレクションの各要素を列挙しての処理

Transcirpt open.
#(a b c) do: [:each | Transcript space; show: each]


インデックスを使って順序付きコレクション(配列など)の各要素を列挙する処理をしたい場合は、1 から要素数の範囲(Interval のインスタンス)の各要素について上記の処理を記述し、ブロック内で対象となるコレクションの要素にアクセスする方法をとります。

| array |
array := #(a b c).
Transcirpt open.
(1 to: array size) do: [:idx | Transcript space; show: (array at: idx)]


この種の繰り返し処理専用に、#to:do: というメソッドもあります。

| array |
array := #(a b c).
Transcirpt open.
1 to: array size do: [:idx | Transcript space; show: (array at: idx)]


見た目のうえでは括弧の有り無しだけの違いように思えますが、前者においてレシーバが (1 to: array size) の返値の範囲オブジェクトで、メッセージが do: [...] 、セレクターが #do: であるのに対し、後者はレシーバーが 1 で、メッセージが to: array size do: [...] 、セレクターが #to:do: …であるという式の意味では大きな違いがあります。この場合に限らず、メッセージ式を見たら常に、レシーバーは誰、メッセージはどんなで、セレクターは何、と解釈する習慣をつけておく(つまり、脳内にメッセージ式パーサーを用意しておく)ことは Smalltalk を最速でマスターする上で非常に大切な一歩となります。


■環境のインストール

Squeak開発者版 ページから all-in-one パッケージ Squeak3.10.2J-all-in-one.zip をダウンロードして展開するのが簡単で便利です。


■環境の起動

Win では win_run.bat 、Mac では mac_run.command 、Linux では linux_run を使います。詳細は ReadMe-ja.txt を参照してください。


■式の評価

  1. デスクトップ部分(青い背景部分)をクリックしてメニューをポップアップ(以後、デスクトップメニュー)
  2. 「開く…」を選ぶ
  3. ワークスペース」を選ぶ
  4. (ウインドウが開くのでそこで)「3 + 4」とタイプして入力
  5. 「3 + 4」部分をドラッグして選択
  6. 右クリック(ホストOSと環境の組み合わせによってはホイールボタンクリック)してメニューをポップアップ(以後、右クリックメニュー)
  7. 「式を表示 (p)」(以後、print it) → 結果の「7」が式の直後に挿入される


補足

  • 評価したい式が複数行でなければ、式はドラッグして選択する必要はありません。そのまま print it できます。
  • 返値を破棄してよい場合は「式を評価 (d)」(以後、do it)を選びます。
  • print it と do it にはキーショートカットもあります(alt + p と alt + d。Mac は alt の代わりにコマンドキー)。
  • REPL はありません。式を入力 → 範囲選択(必要な場合) → print it もしくは do it …という操作に慣れてください。

ワークスペース変数と注意

ワークスペースでは、Smalltalk で使える変数の中で唯一宣言無しで使用できる「ワークスペース変数」が使えます。ワークスペース変数は、一度代入した値を半永久的に保持するので、特定のオブジェクトを繰り返し使用する場合に、テンポラリ変数やグローバル変数より便利に使えます。反面、テンポラリ変数宣言に含まれない初出の変数をすべてワークスペース変数とする性格上、コンパイラの変数名チェック機能を無効化してしまうことになり、結果、期待しない動作を引き起こすミスタイプを見過ごしてしまうこともしばしば起こりえます。慣れないうちは、ワークスペース変数は無効にしておくことを個人的には強くお薦めします。ワークスペース変数の使用不使用の設定は、各ワークスペースウインドウのクローズボックス右隣にあるウインドウメニューから automatically create variable declaration を選んでトグルできます。


■覚えておくとよい設定式

  • メニューを英語に(メニュー項目を do it や print it のように表示できます)。 …… 次式を do it
    Locale switchToID: (LocaleID isoLanguage: 'en'); currentPlatform: (Locale isoLanguage: 'ja')
  • マウスの右クリックとホイールクリックの機能を入れ替える(ホストOSによっては、ホイールクリックが右クリックになってしまう問題を回避)。 …… 次式を do it
    Preferences togglePreference: #swapMouseButtons

■環境の保存

環境の状態を保持して、次回起動時に反映できます。

  • デスクトップメニュー → 保存


終了時(次項)に「保存して終了」を選ぶ方法もあります。


■環境の終了

  • デスクトップメニュー → 終了…


「終了する前に変更を保存しますか?」に対して、次回も同じ状態で続けるなら「はい」 前回保存時の状態で始めたいなら「いいえ」を選びます。


■参考図書

自由自在Squeakプログラミング

自由自在Squeakプログラミング

自由自在 Squeakプログラミング PDF版