FizzBuzzクイズ-Squeak/Pharo Smalltalk解答例


「FizzBuzzクイズ」クイズ-Ruby編 の解答例では、Rubyのモジュールを使ってトリッキーな実装を試しましたが、Smalltalkにもトレイトと呼ばれる多重継承機構を有した処理系(Squeakやそこから派生したPharo)があるので、Rubyで使ったのと似た方針で実装してみました。

Squeak Smalltalk(Squeak5.1)向けの実装例

squeak.org にアクセスし、macOS、Windows、Linux 用の処理系を収めた .zip を展開すればインストール完了です。.app、.exe、.sh を使って環境を起動できます。

なお、不慣れな人でもシステムブラウザを使わずに試せるように、Tools → Workspace でワークスペースを開き、そこにペースト後、シンプルに式を選択して評価(alt/cmd + d。結果が欲しい場合は alt/cmd + p )できるコードで書いています。

Trait named: #FizzBuzzQuiz uses: #() category: 'FizzBuzz-Quiz'.

FizzBuzzQuiz compile: 'isDivisibleBy: m
   ^(Processor activeProcess environmentAt: #fbValue) isDivisibleBy: m'.
FizzBuzzQuiz compile: ', str
  Processor activeProcess environmentAt: #fbValue put: self.
  ^str'.

FizzBuzzQuiz compile: 'fizz ^(self isDivisibleBy: 3) ifTrue: [self, ''Fizz''] ifFalse: [self]'.
FizzBuzzQuiz compile: 'buzz ^(self isDivisibleBy: 5) ifTrue: [self, ''Buzz''] ifFalse: [self]'.

{Number. String} do: [:each | each uses: FizzBuzzQuiz].

1 fizz buzz. "=> 1 "
3 fizz buzz. "=> 'Fizz' "
5 fizz buzz. "=> 'Buzz' "
15 fizz buzz. "=> 'FizzBuzz' "
15 buzz fizz. "=> 'BuzzFizz' "
14 fizz buzz. "=> 14 "

FizzBuzzQuiz compile: 'pezz ^(self isDivisibleBy: 7) ifTrue: [self, ''Pezz''] ifFalse: [self]'.

7 fizz buzz pezz. "=> 'Pezz' "
21 fizz buzz pezz. "=> 'FizzPezz' "
35 fizz buzz pezz. "=> 'BuzzPezz' "
105 fizz buzz pezz. "=> 'FizzBuzzPezz' "
105 fizz pezz buzz. "=> 'FizzPezzBuzz' "
105 pezz buzz fizz. "=> 'PezzBuzzFizz' "
104 fizz buzz pezz. "=> 104 "

Pharo Smalltalk(Pharo6.1)向けの実装

pharo.org から処理系をダウンロードして起動し、Squeak版と同様に、デスクトップクリック → Playground でプレイグラウンドを開いてペースト後、選択して ctrl/cmd + d もしくは ctrl/cmd + p で適宜評価することで試せます。

Trait named: #FizzBuzzQuiz uses: #() category: 'FizzBuzz-Quiz'.

FizzBuzzQuiz compile: 'isDivisibleBy: m
   ^ProcessSpecificVariable soleInstance value isDivisibleBy: m'.
FizzBuzzQuiz compile: ', str
   Processor activeProcess psValueAt: ProcessSpecificVariable soleInstance index put: self.
   ^str'.

FizzBuzzQuiz compile: 'fizz ^(self isDivisibleBy: 3) ifTrue: [self, ''Fizz''] ifFalse: [self]'.
FizzBuzzQuiz compile: 'buzz ^(self isDivisibleBy: 5) ifTrue: [self, ''Buzz''] ifFalse: [self]'.

{Number. SequenceableCollection} do: [:each | each addToComposition: FizzBuzzQuiz].

1 fizz buzz. "=> 1 "
3 fizz buzz. "=> 'Fizz' "
5 fizz buzz. "=> 'Buzz' "
15 fizz buzz. "=> 'FizzBuzz' "
15 buzz fizz. "=> 'BuzzFizz' "
14 fizz buzz. "=> 14 "

FizzBuzzQuiz compile: 'pezz ^(self isDivisibleBy: 7) ifTrue: [self, ''Pezz''] ifFalse: [self]'.

7 fizz buzz pezz. "=> 'Pezz' "
21 fizz buzz pezz. "=> 'FizzPezz' "
35 fizz buzz pezz. "=> 'BuzzPezz' "
105 fizz buzz pezz. "=> 'FizzBuzzPezz' "
105 fizz pezz buzz. "=> 'FizzPezzBuzz' "
105 pezz buzz fizz. "=> 'PezzBuzzFizz' "
104 fizz buzz pezz. "=> 104 "

解説

SqueakやPharoのトレイトは、Rubyのモジュール(あるいはScalaのトレイト)と違い、継承パスには参加しません。その代わり、メソッドの集合としてクラスに組み込んで使います。今回は関係ないので以下は余談ですが、複数のトレイトを使う場合は平滑化(フラット化)して仮想的なひとつのトレイト(トレイトコンポジションと呼ぶ)を作り、それをクラスに組み込んで使うことになります。また、メソッドの衝突がある場合はこの平滑化のタイミングで解決しておく必要があります。これがRubyのモジュールやScalaのトレイト(紛らわしい…)等が属するミックスイン機構と、Smalltalk(やPHP)のトレイト機構との違いです。

トレイトに定義されたメソッドは、それを使用する(組み込む)クラスに定義済みの同名メソッドがある場合、遮蔽されます。つまりRuby版で使ったのと同じ手がSmalltalkのトレイトでも使えるわけです。

Squeak/Pharoには、割り切れるかどうかを調べる #isDivisibleBy: というメソッドがNumberに定義されているのでこれをRubyのときの modulo と同様に多態させました。一方、文字列の連結に使う#,(カンマ)というメソッドがSqueakにはStringに、PharoにはSequenceableCollectionに定義されているのでこれをRuby版の concat と同様に多態させています。

Ruby版ではインスタンス変数を使いましたが、Squeak/Pharoではインスタンス特異的にインスタンス変数を適宜用意することができないので、グローバル変数の代替としてスレットローカル変数を使うことにしました。クラスへのトレイトの組み込み(Rubyのモジュールでいうところのインポート)同様、SqueakとPharoではAPI等が異なるのでコードはそれぞれにあわせて違えてあります。

以上、誰トクな解説でした。