循環を使った FizzBuzz を Squeak Smalltalk で


この方針では以前も書いたような気がしますが、Squeak Smalltalk で無理なく書くとこんな感じになりますか。

| fizz buzz |
fizz := [:n | #('' '' 'Fizz') atWrap: n].
buzz := [:n | #('' '' '' '' 'Buzz') atWrap: n].
(1 to: 100) collect: [:n | (fizz value: n), (buzz value: n) ifEmpty: [n]]


無限列(循環列)とかかっこいい機構はないうえに、Smalltalk は多くの言語にはある fun(n) という関数コールのための構文を持たないため、value: n が続くのがなんだかなーって感じもするので、fizz buzz が関数的に振る舞うのを諦めてしまって、いっそ、このように書いてしまったほうが潔いかもしれません。

| fizz buzz |
fizz := #('' '' 'Fizz').
buzz := #('' '' '' '' 'Buzz').
(1 to: 100) collect: [:n | (fizz atWrap: n), (buzz atWrap: n) ifEmpty: [n]]


そこでこんなひと工夫。関数(ブロック)が返す文字列(とは限らないけれどとにかくその結果)を結合(#,)して返す関数(ブロック)を改めて返してくる BlockClosure>>#, を次のように別途定義してみましょう。

BlockClosure >> , other
   ^[:val | (self value: val), (other value: val)]


これを使うと、元のコードもほんの少しだけシンプルになって、次のように書けるようになるわけですが―

| fizz buzz |
fizz := [:n | #('' '' 'Fizz') atWrap: n].
buzz := [:n | #('' '' '' '' 'Buzz') atWrap: n].
(1 to: 100) collect: [:n | (fizz, buzz value: n) ifEmpty: [n]]


ただ、Smalltalk の #, は配列や文字列の結合(#(1 2 3), #(4 5) "=> #(1 2 3 4 5) ". 'abc', 'de' "=> 'abcde' ")であることから、相手が関数であれば個人的には関数の合成を連想してしまうので、これだとちょっと違うような気もします。

一方で、たとえば 7 で割り切れるときの Pezz の追加とかはラクなのでこういう拡張時のコード追加がシンプルになる点に関してはすごく好みではありますね。

| fizz buzz pezz |
fizz := [:n | #('' '' 'Fizz') atWrap: n].
buzz := [:n | #('' '' '' '' 'Buzz') atWrap: n].
pezz := [:n | #('' '' '' '' '' '' 'Pezz') atWrap: n].
(1 to: 105) collect: [:n | (fizz, buzz, pezz value: n) ifEmpty: [n]]