読者です 読者をやめる 読者になる 読者になる

おがさわらなるひこのオープンソースとかプログラミングとか印刷技術とか

おがさわらなるひこ @naru0ga が技術系で興味を持ったりなんだりしたことをたまーに書くブログです。最近はてなダイアリー放置しすぎて記事書くたびにはてな記法忘れるのではてなブログに移行しました。

クリエイティブ・コモンズ・ライセンス
特に断りがない場合は、本ブログの筆者によるコンテンツは クリエイティブ・コモンズ 表示 - 継承 4.0 国際 ライセンスの下に提供されています。

Smalltalk勉強会2009.11参加録&私的Smalltalkすげー論

Event ProgrammingLanguage

最愛の言語が Prolog で今もっともホットな言語が Smalltalk ってどこの昭和の人? って感じです。ぼくも Java とか C# とか golang とか書けた方がいいですか? ま、やる気さらさらないですが。

ということで Smalltalk 勉強会行ってきました。
講師の abee2 さん、幹事の umejava さん、会場提供のシンコムシステムズジャパンさん、ありがとうございました。

内容は「Squeak で SCRATCH をハック!」という素敵な内容。
でもハンズオンだから全部は書かない予定……だったんだけどけっこう書いてしまった。

資料のリンクを貼っておきましょう*1
Scratch Project

SCRATCH というのは

まあ受け売りですが、

  • Squeak Etoys の MIT 流解釈
    • MIT 流ということはすなわちシーモア・パパート (LOGO の人) 直系といってよいか。
  • 詳しくは @IT の連載見たほうがいいっす。入門記事としてはとてもよくかけてます。
  • そしてこの子供たちのための教育プログラミング環境 SCRATCH にあーんなことやこーんなことをしちゃおうというのが今回の企画。

てなわけで SCRATCH を hack

かように SCRATCH は非プログラマ(主に子供)を対象にしたプログラミング環境なのですが、その実態は Squeak*2 で動作するプログラムであり、ソースコードも (コピーライトに抵触するものを除いた形で) こちらで配布されています。

ということで Squeak でプログラミングさえできれば SCRATCH はハックできてしまうということなわけですね。
そこで、資料の6ページ目になるわけです。

Squeak プログラムの掟
  • ハロを出せ
  • インスペクタを開け
  • 怪しげなセレクタをブラウズしろ
  • インプリメンタを見ろ
  • センダも見ろ
  • コードをコピペしろ
  • 書いたら試せ

実践!実践!

えーとごにょごにょやってソースコードを展開するとこんな感じの画面になります。

一見普通の SCRATCH の画面ですが(英語ですけど)、よく見ると後ろにこっそり「Workspace」とか「Browser」とかいますね。
これは Squeak 環境の上でアプリケーションとして動いてる SCRATCH なわけです。
Squeak 環境の上でだって普通に SCRATCH として遊べます。

さて、ここで左側にいる「パレット」を増やしてみましょう。

具体的には「move ( ) steps」と「turn [右回り] ( ) degrees」を組み合わせて「move () and turn right ( )」(長いので単位略)というのを追加してみたい。みたいでしょ? みたいと言え。

そこで先ほどの鉄則を実行していきましょう。
あ、その前にもともとの奴をぶっ壊しちゃうとアレなので新たなワークスペースを開きましょう。
詳しい画面遷移は省きますが、背景で左クリック(Smalltalk 的には赤ボタンクリックというべきか) してワールドメニューを出し、Open -> Morphic Project とすると Unnamed1 というちっちゃなウィンドウが出ます。こいつをビシッと赤ボタンダブルクリックすると画面全体に広がります。さらにここでワールドメニュー -> Open -> SCRATCH とすると SCRATCH の画面がこんにちわします。この上で作業をすすめましょう。

1.ハロを出せ

「ハロ」とはガンダムに出てくるアレではなく、Squeak における超基本概念でございます。
Smalltalk な世界では画面に出てる物も全部オブジェクトなのですが、Squeak だと*3 こいつらはみんな Morph と呼ばれるものになっています。
こいつの上でマウスの「青ボタン」*4 を押すと、「ハロ」と呼ばれるいろんな機能を持ったボタンが出てくるのです。こんな風に。

ちなみにこれは Squeak 3.9 の画面で、SCRATCH は Squeak 2.8 ベースなのでちょっと違いますけど。

ということで、SCRATCH のそれぞれのパレット上のアイテムも Morph だということはハロが出せるわけです。

で、なんのためにハロを出すか。
それは、パクりたいものの実装を見るためです。
再利用だなんてかっこつけずパクりと素直に言うべし。

今回の場合、「move () and turn right ( )」という二引数のパレットを追加したいわけですよね。
ということは、他の二引数のパレットをパクればいいわけです。
見てみると……「goto x:() and y:()」てのがある。
じゃあこれのハロを出します。

こいつにフォーカスを当てて青ボタンを押すと、最初は SCRATCH 全体のフィールドにハロが出ます。構わず連打してるとこのボタンの周囲にハロが出るはずです。見にくいけど名前も出てます。

こいつの右上やや下(ややこしいな)にあるスパナマークにマウスカーソルを合わせると「Debug me.」と出てます。

ので、はい、ここで次の鉄則。

2.インスペクタを開け

ということでクリックしたメニュー

を見ると、「Inspect Morph.」とか怪しい奴がいるので、こいつをクリック。

はい、なにやら出てきました。
CommandBlockMorph ってオブジェクト(のインスタンス)をブラウズしてるんでしょうね。きっと。
このペースで書いてると一生終わらないので次のステップ。

3.怪しげなセレクタをブラウズしろ

セレクタだから綴りは selector でしょうから左側のペインに移動して s 連打。selector 発見。したのが上の画面。

セレクタってのはなにかっていうと、実はオイラよくわかっていません。
ので Workspace に移動して、

CommandBlockMorph browse

とタイプして選択して Alt-D (Do it) してみると、

BlockMorph subclass: #CommandBlockMorph
  instanceVariableNames: 'commandSpec
                          argPermutation
                          argMorphs
                          titleMorph
                          receiver
                          selector
                          isReporter
                          isTimed
                          wantsName
                          wantsPossession
                          numberArgFlags '
	classVariableNames: ''
	poolDictionaries: ''
	category: 'Scratch-Blocks'

と出てきます。これが CommandBlockMorph クラスの定義。
これを見てもインスタンス変数であることしかわかんないんですが、さっきの画面を見ると #gotoX:y: とか入ってたところを見ると、なにかの行動(多分ダブルクリックかな)をすると反応するメソッドを保持してるんじゃないかなーと思います。想像ですが、いいんです! ちゃんと調べてる暇があったら先進みましょう。

閑話休題
次の二つはいっぺんに。

4.インプリメンタを見ろ
5.センダも見ろ

先ほどのインスペクタの #gotoX:y: をちょちょいとダブルクリックすると # を除いた文字列が選択されるので、この状態で Implementer は Alt-M、Sender は Alt-N で見られます。
こっちがインプリメンタで

こっちがセンダ。

インプリメンタは名前のとおり「そのメッセージを受け取る処理を実装してるところ」で、今回は候補は一個だけど、クリックすると実際の実装が見られます。

センダもこれまた「そのメッセージを送っているところ」であって、これは候補が三つあります。これをぱらぱらっと見てそれっぽいのを探すんですが、まずは ScratchSpriteMorph class blockspecs を見てみると、

blockSpecs

  | blocks |
  blocks _ #(
    'motion'
      ('move %n steps'        - forward:)
      ('turn %n degrees'        - turnRight: 15)  "icon shows turn direction"
      ('turn %n degrees'        - turnLeft: 15) "icon shows turn direction"
      -
      ('point in direction %d'      - heading: 90)
      ('point towards %m'       - pointTowards:)
      -
      ('go to x:%n y:%n'        - gotoX:y: 0 0)
      ('go to %m'           - gotoSpriteOrMouse:)
      ('glide %n secs to x:%n y:%n' t glideSecs:toX:y:elapsed:from: 1 50 50)
      -

ん?
さっきのパレットの並びと比較して、

なんかそれっぽくね?
並び順も一緒だし、"-" が入ってるところはちょうど空白が入ってる。
それっぽいと思ったら早速実践です。

6.コードをコピペしろ
7.書いたら試せ

つーことでさっきの奴に一行足してみましょう。
処理そのものはまだ作ってないので、そのまんまにしておきましょう。

blockSpecs

  | blocks |
  blocks _ #(
    'motion'
      ('move %n steps'        - forward:)
      ('turn %n degrees'        - turnRight: 15)  "icon shows turn direction"
      ('turn %n degrees'        - turnLeft: 15) "icon shows turn direction"
      ('move %n and right turn %n'  - gotoX:y: 0 0) "<-copy from go to x: y: and message changed"
      -
      ('point in direction %d'      - heading: 90)
      ('point towards %m'       - pointTowards:)
      -
      ('go to x:%n y:%n'        - gotoX:y: 0 0)
      ('go to %m'           - gotoSpriteOrMouse:)
      ('glide %n secs to x:%n y:%n' t glideSecs:toX:y:elapsed:from: 1 50 50)
      -

そいでセーブ (Aspect; ALT-S) します。Smalltalk には変更履歴を自分で管理するシステムがあるので、最初のセーブのときにはイニシャルを聞かれますが、そこはまあ正直に入れておきましょう。私の場合は Naruhiko Ogasawara なので NO と。
んで画面を再描画するために「Motion」ボタンをぽちっと押せば。

おお! ちゃんとパレットが増えてる! 素敵!

メッセージの実装

あとはじゃあ実際にこの処理を受け取るところを書くわけです。
さっきの blockSpecs で追加した行をもう一回見ます。

blockSpecs

  | blocks |
  blocks _ #(
    'motion'
      ...
      ('move %n and right turn %n'  - gotoX:y: 0 0) "<-copy from go to x: y: and message changed"
...

想像するに、この gotoX:y: が ScratchSpriteMorph クラスのメッセージセレクタを指しているのでしょう。
したがって例えば forward:andTurnRight: とかいうメソッドを作ればいいんじゃないかしらそうじゃないかしら。
そして 0 0 はきっとデフォルト値に違いない。きっとそうだ。

そこでさっき開いた gotoX:y: のインプリメンタを開いて、念のためもっと詳細な情報を出すためにブラウザを出しておきましょう。インプリメンタで ScratchSpriteMorph class gotoX:y: というところを選んで Alt-B (Browse) するとブラウザがこんにちわします。

上側の左から順にクラスカテゴリ、クラス、メッセージカテゴリ、メッセージという順に並んでます。いくつかは画面外にいるかもしれませんがそこのペインに行ってカーソルで上下すれば出てきます。

ほいでここに forward:andTurnRight: というメソッドを作ってみましょう。
下のペインを見ると、

gotoX: x y: y

  self referencePosition: x@y.

とあります。セレクタの表現だと gotoX:y: となっていましたが、これに引数の名前がついて実際のメッセージの宣言になるわけですね。
ということは多分メッセージは forward: (引数1) andTurnRight: (引数2) となるんでしょうが、この引数の名前どうしましょう? むろん適当に決めてもいいんですが、せっかくなら「move」アイコンや「turn」アイコンのセレクタのそれと合わせた方がかっこいいとおもいません?
ということでさっきの blockSpecs に戻ると、move のセレクタは forward: で、turn (右回転) のセレクタは turnRight: だということがわかります。それぞれのインプリメンテーションを確認すると、forward: distance と turnRight: degrees だということが分かります。
じゃあここで作るメッセージは forward: distance andTurnRight: degrees になるんじゃないかな。
ということで、さっき gotoX:y: の定義が出ていたところをざくっと全部消して(!)、次のように書きます。

forward: distance andTurnRight: degrees
  self halt

self halt はブレークポイントを設定するための命令です。要は呼ばれてることを確認したいということですから。もちろん Alt-S でセーブを忘れずに。ブラウザでちゃんと forward:andTurnRight: が追加されてることが確認できると思います。
もちろんざくざくっと消してしまった gotoX:y: も残ってます。

おっと、blockSpecs も直さないとですね。

blockSpecs

  | blocks |
  blocks _ #(
    'motion'
      ...
      ('move %n and right turn %n'  - forward:andTurnRight: 10 15) "NEW!"
...

デフォルト値も書き換えておきました。それぞれ move と turn (右) の初期値のパクリです。

ん? 今見てる blockSpecs って gotoX:y: のセンダとして開いた奴だよね? そこでバリバリ編集しちゃっていいの?

いいのです。Smalltalk の素敵なところはここにもあって、パクるために開けたブラウザからバリバリ書き換えると、ちゃんとしかるべきように書き換えることができるわけです。
さっきの move:andTurnRight: の方もそうで、属しているクラスやメッセージカテゴリはそのままに、新しいメッセージをひょいっと追加できるのです。素敵♪
ついでに「あれ、このクラスどう使うんだっけ?」と迷ったらテストコード書いて選択して Do it とか Print It とか、先ほどみたいにブラウザ開けたりセンダやインプリメンタ見ることも自由自在です。要はエディットできる画面では共通になんでもできてしまうので、やりやすいところでやればいい。実装はこちら、インタプリタ的な機能はこちら、とか分けなくていい。この自由さは感動モノです。

んでまた SCRATCH の画面に移動して Motion ボタンを押してリロードして、追加したボタンをクリック!

デバッガ出ましたね。下のペインはコールスタックです。
見るとScratchSpriteMorph の halt で止まってて、それを呼んでるのはScratchSpriteMorph の move:andTurnRight: だとなってます。
よし、ちゃんとあってる。あとは中身を実装するだけだ。
中身ったって、もとからある動作の組み合わせですから、これでいいですよね。

forward: distance andTurnRight: degrees
  self forward: distance.
  self turnRight: degrees

これをセーブして念のためSCRATCHのMotionボタンも押してリロードしといて、えいっ。クリック!
やた! ネズミがちょっと動いて向き変えた!
出来た出来た!


というのをハンズオン形式で、経験者とペアプロ形式でやりました。
基本的には僕がキータイプをして、ペアの経験者さんがアドバイスをくれるというスタイル。

この課題が最初。
次の課題は「今何月何日!」「今xx時xx分!」と叫ぶパレットを作る。
余裕がある人はオマケで書いた課題。

アドバイスが的確だったこともあって、非常にスムースに終わりました。
でもね、アドバイスといっても、インプリメンタとセンダのショートカットの覚え方とか、ウィンドウがたくさん出てきて収集がつかなくなったときはシフト+背景クリックでウィンドウメニューが出るよとか、Smalltalk 的には命名規則はこうしたほうがいいとか、PrettyPrint (成形) 機能が使えるよとか、操作面ばかり。
どこにどういうクラスがあってメソッドはこうで、という説明はいっさいしてもらってません。
Web 検索もしてませんし参考書の類も見てません。
ただひたすら分からないことを Smalltalk に聞きつづけただけです。
すごいと思いません???

んで、

やっぱ「Smalltalk すげー」と思ったのがその環境のパワフルさね。

はっきりいってしまえば、言語仕様は確かにシンプルだけど演算子すらないのはちょっとやりすぎ感がただようし*5普通の環境であれば猛烈に巨大なクラスツリーはちょっと圧倒されちゃうでしょう。だから、言語としての Smalltalk ってそんな、むちゃんこ優れているわけじゃないと僕は思う。言語だけだったら ruby のほうが好きかも。

でも環境まで含めると Smalltalk が格段にすごく見えてきます。
ブラウザとかインスペクタの類が異常に充実してるしインタプリタ環境なのですぐに試せるので超ラクチン。まさに「Smalltalk のことは Smalltalk に聞け!」なんです。
特に Morphic の場合、ハロがいるので「画面に見えてる奴全部がオブジェクト」って感覚がわかりやすいし、パクリがしやすい。ハロでできることはきっとメッセージ投げてもできるんだろうな、って想像がついたり、たとえばGUIアプリケーションを作りたければ、WorkspaceとかTranscriptとかのウィンドウ開けて、ハロで調べて、そいつらがどう呼び呼ばれしてるかを調べて、処理をパクればいいんですから。

今のオブジェクト指向開発環境ならデバッガで止めて変数の中身がオブジェクトであってもそれを正しくインスペクトすることぐらいはできるでしょう。でもその変数の中身のオブジェクト (= self) に試しにメッセージを投げてみることとかできる? それがごくふつーにできちゃうところがすごいのです。

僕はまだデバッガ使い慣れてないけど、デバッグデバッグで止めてる画面の中でコード書き換えて「続行」ってやるとスタックフレームとか保持したまんまそのままプログラムが走る。またバグって止まったら書き換えて再実行、ってやってるとデバッガの上でコードかけちゃう。

この自由度、MSDN 引きながら Visual Studio のコード補完とかで C++ 書くのが馬鹿馬鹿しくなる*6
これに比べると好きなエディタでコードかけないとか瑣末事に過ぎない。いやまあ、確かに慣れないうちはエディットのミスオペ多いんですが、それ以上に喜びの方が多いのでね〜。まあ私が Emacsvim をそれほど使いこなしていないからという理由もあるけど。

そのすごさを少しでも分かっていただくため、シロートの私がちょこっとコードを書く例をネタにエントリをでっちあげようという内容になる予定……だったのですが、ハンズオンの内容を思った以上に書いてしまったため、別エントリにしますね。期待してた人ごめんなさい。

おまけ

子供たちの楽しいお友達 SCRATCH がこんな恐ろしい子になってしまいました。きやー!

……詳細は解説しません。でもこういうことするのも簡単なんです (今やってみたら10分ぐらいでできました)。あー楽しい!

*1:リンク先行けば分かるけど、この資料自体 SCRATCH で書かれています。

*2:Smalltalk の処理系の一つ。Apple Smalltalk という幻のプロジェクトの成果が今でも成長を続けているもの。Morph という GUI フレームワークが特徴。MITライセンスによるフリーソフトウェア

*3:Morphic だと、というのが正解なのかな?

*4:実際どのボタンにバインドされてるかは適当な Squeak の参考書を見てください。

*5:例えば 1 + 2 は二項演算子ではなく + という「二項メッセージ」が引数2をレシーバ1に渡しているだけ。演算子じゃないので演算子強度が定義できない。だから 2 + 3 * 4 = 20 != 24 になってしまうのです。

*6:例が古いのは許してね。今なら EclipseJava なのか。他の人と話したら「Smalltalk に慣れてると苦痛」だそうですが