これを実際にSmalltalk(Squeak)で書くとこんな感じになります。 pic.twitter.com/5TbLgPVltO
— アベ先生 (CV: 阿部和広) (@abee2) 2018年10月5日
先日書いた記事に返事を頂いて、そうか、Scratch 1.4 の中身は Smalltalk(Squeak)なのだからそっちで試してみることができるんだ。ということで、ちょっと Smalltalk を数日動かしてみる。
何処から手を付ければよいのか?
真面目にやるのであれば、教科書とかチュートリアルをやればいいんでしょうが、そもそも Smalltalk を今更まじめに見る気はないし(と思っておりましたよ)、オブジェクト指向のメッセージングのところだけ見ておきたいので、何かよい実例はないのかと思い探してみました。Google で初心者解説回りをさがしてもよいのですが、今だとアドベントカレンダーを見ていくとなんとなく実用がわかります。
Smalltalk Advent Calendar 2017 – Qiita
https://qiita.com/advent-calendar/2017/smalltalk
初心者なところと、難解で細かいいところと雑多な記事があつまっていますが、少なくと、
- ほどよく、プログラム言語を使っている人が書いている
- ほどよく、複数の人が書いている
ということで、アドベントカレンダーの各記事と各リンクはプログラム言語を学ぶときのとっかかりとして便利です。
テスト駆動開発でお試しする Pharo Smalltalk・第1回 はじめてのレッド、イエロー、グリーン – Qiita
https://qiita.com/sumim/items/fa41066c57d211814ff9
そんな中で、TDD を使った解説があったので試していきます。
Scratch 1.4 で Smalltalk を使う
ちなみに、Scratch 1.4 で Smalltalk の環境を開くには、以下の記事を参考にしてください。
SmalltalkからScratchをいじる(1) スプライトの大量生成 – Qiita
https://qiita.com/maeda_/items/7a525cae6b3c1ca45773
「画面右上のSCRATCHのロゴの”R”の部分をShiftキーを押しながらクリックするとメニューが開きます。」ってのがスタートです。
- inspect morph がスクリプトを持つ変数表示と workspace/playground の表示
- browse morph class でクラスのブラウジングの表示
このままでも色々できるはずなのですが、もうちょっときちんとして環境が欲しいので、Pharo ってのを入れます。
Pharo 6.1 を使う
Pharo https://pharo.org/ ってのは、OSS な Smalltalk な環境で、Windows/macOS/Linux での動作環境が用意されています。先の TDD の解説も Pharo なので、これを使います。
Smalltalk は内部では VM で動いているので、Android シミュレータのようにイメージをダウンロードして実行します。で、最新の公開は 7.0 らしいのですが、stable なのは 6.1 のほうですね。7.0 と 6.1 はメニュー表示が微妙に違っていて、先の TDD の記事と違ってしまうので、6.1 のほうを入れます。
間違えて 7.0 のほうを入れて「メニューが違う」「リファクタリングがない」と思って苦労したのは内緒です。↓が 7.0 の画面
これが 6.1 の画面。ちょっと違う。
まず最初にやってことは、コードエディタの文字が小さいので大きくするところから、背景部分をクリックして「System」→「Setting」を開きます。
何処あるのかわからないので、Font で検索して、Default と Code の部分を変更します。大きめにしておくと老眼の私には優しくなります。
TDD のチュートリアルを進める
テスト駆動開発でお試しする Pharo Smalltalk・目次 – Qiita
https://qiita.com/sumim/items/3a36c03ab41519077b37
を地味に進めるわけですが、まったく Smalltalk の文法が分からないながらもいくつか面白い発見をしたので、以下、書き残しておきます。Smalltalk 自体は古い言語(メジャーなのは Smalltalk-80 位からだから、40年ぐらい前か)ので、そのあとに出てきた言語も含めてってことですが。
文末に「.」ピリオドを使う
C言語系では文の終わりに「;」セミコロンを使うわけですが(これが嫌われものであり、セミコロンあるなしで、関数言語とか Python とかに移る場合もあるわけで)、Smalltalk は「.」ピリオドで文章を終わらせます。なるほど、英文はピリオドで終わるから、Smalltalk もピリオドで終わるのですね(おそらく)。
関数の最後はピリオドが省略できるので、一文だけの関数はピリオドがあったりなかったりします。
戻り値を ^ で表す。
最初、この「^」はなんだ?と思ったのですが、return です。関数型言語だと、常に式を表すので関数の最後の行が価を返すことが決まっているのですが、Smalltalk の場合は、明示的に値を返します。
メソッドのパラメータを「:」コロンで区切る
Smalltalk でメソッド(関数)を呼び出すときに、括弧を書きません。引数の指定に「:」コロンを使うわけですが、なんか、この形式みたことがあるな?と思ったら Objective-C でした。
というか、Objective-C のルーツが Smalltalk であったことを今知りました。
- Smalltalk の場合
bank addXRate: 1 USD / 2 CHF . - Objective-C の場合
[ bank addXRate: 1 USD / 2 CHF ] ; - C++ の場合
bank.addXRate( 1 USD / 2 CHF ) ;
「1 USD / 2 CHF」は、まあ、ひとつの引数とみてください。Objective-C の場合、ひとつ目の引数の場合は名前(セレクタ)を省略することもできるのですが、ここではあえて使うパターンで。
2つのパラメータを持つときは、
- Smalltalk の場合
bank exchange: 10 CHF to: #USD . - Objective-C の場合
[ bank exchange: 10 to: #USD ] ; - C++ の場合
bank.exchange( 10, #USD );
こんな感じになりますね。C/C++ だと引数に名前がつかないので、型で判断するのでややこしくなるのですが、Smalltalk の場合は名前が使えます。この部分、C# では名前付きの記法もできるようになったので、似た形で書けます。
ちなみに self は自分自身のオブジェクトを示すので C++ の this と同じ。
- self assert: ( bank exchange: 10 CHF to: #USD ) equals: 5 USD
- this->assert( bank.exchange( 10, #USD ), 5 USD );
この2つは同じ意味になります。
Smalltalk には型がない
実は Smalltalk には型がありません(と言い切っていいのだろうか?)。関数の後ろにある「|」パイプで囲まれたところが、内部変数の宣言です。えらい大雑把ですが、Smalltalk が VM で動いているからこれでいけるみたいです。
VB6 の Variant みたいな感じですかね。
- Dim bank, result
と書くのと同じことになります。ちなみに、下記のように bank 変数に別の型を入れてもエラーになりません。あまり、やらないだろうけど。
既存クラスを拡張できる
たぶん、Smalltalk のオブジェクト指向プログラミングの肝になると思うのですが、Smalltalk では既存のクラスにメソッドを追加して拡張できます。Objective-C でいうカテゴリだったり、C# でいう拡張メソッドのような機能ですが、Smalltalk ではすでに定義されているクラスに対してメソッドが追加できます。
既存のクラスを弄ってしまうのだから、セキュリティやら隠蔽性やらはどこに行くの?という話なのですが、そこは Smalltalk が VM 上で動いている利点なのでしょう。動作環境が別々なのだから大丈夫…という思想だったのかもしれません。
ともかく、一番根っこになる Object クラスにもメソッドを追加できます。
Object クラスの testing のカテゴリ(これは分類なので、何処にあっても一緒(だと思う)には、isColor とかいうメソッドもあるわけで、所謂「神クラス」になります。Objective-C にもカテゴリ名を使って既存クラスを拡張する機能があってので、これと同じ機能を取り入れたものと思われます。
C++ がオブジェクト指向で GUI を作るときに継承を使い、そのあと深い継承が深くなりすぎて C# で委譲が使われることが多くなった、という経緯があるのですが、Smalltalk だと根っこに手を入れるという技があったのですね。まあ、このあたりは C++/Java/C# 等ではできないし、親クラスがいきなり変わってしまっても困るので(メソッド名の重複など)拡張という機能だけに留まるわけですが。
細かく保存し、細かくコンパイルする
Smalltalk では、ブロックという概念がない(らしい)ので、クラスのメソッド単位でコードを書いていきます。メソッドが細かく分かれるので、分かれた単位で保存&コンパイル(Accept)を繰り返します。これ、昔は相当重かったと思われるのですが、今の CPU ならば全然問題ありません。
記述するコードは十数行ぐらいなので、これで十分かもしれません。逆に言えば、ひとつのメソッドに数百行も書いてしまっていたら、Smalltalk としてはおかしいかもしれません。
if 文ってのは無い
これも Smalltalk の奇妙な文法だと思うのですが、Smalltalk には if 文がありません(たぶん)。じゃあ、条件分岐はどうなるのか?というと「ifTrue:」と「ifFalse:」というセレクタ(引数みたいなものか)を使います。
なので、C++ であれば
1 | if ( a == true ) { b = 10 ; } else { b = 20 ; } |
と書くところを、Smalltalk では、
1 | a == true ifTrue: [b := 10] ifFalse: [b := 20] . |
と書きます。不思議ですね。この書き方、実は利点があって、if 文(っぽいもの)が式として扱えるので、関数型言語っぽいことができます。
1 | b := a == true ifTrue: 10 ifFalse: 20 . |
な感じで、a が true/false かによって、10 か 20 を b に代入ができます。
~~~
そんな訳でひとまず、気づいたところはここまで。あともう少し TDD をぽちぽちと進めるつもり。