ふるつき

v(*'='*)v かに

J言語でFizzBuzz

※なんでもかんでも「演算子」「関数」と言っていますがこれは半分くらい関数じゃないやつを含んでいます

(":]`[@.(''-:])'FizzBuzz'#~4 4#(0=3&|),0=5&|)"0  >:i. 100

これだと説明が難しいので括弧をつけておきます。

((":)(]`[@.(''-:]))('FizzBuzz'#~(4 4)#((0=3&|),(0=5&|))))"0  >:i. 100

これで(1, 100)区間に対してFizzBuzzが走ります。以下解説。色々説明しないといけない前提条件が多くてややこしくなりますがご容赦ください。

演算子の優先順位はなく、右結合

>:i.はそれぞれ関数であり、>:i.100(>:(i. 100))という順序で評価されます。

そして、 i. 100(0, 100]の半開区間の整数列を生成し、>:はインクリメントを行うので、>:i.100の評価結果は1 2 3 4 ... 100 になります

bonding

要するに仮引数が存在できるという話で、dyadicな関数x f y((J言語では関数はf xx f yの2種類の呼び方があって、それぞれmonadicdyadicと呼んで区別します。まあオーバーロードのようなものです))と評価したい関数があって、xは決定してるけどyは未定というときはx&fと書いておくと、これ自体がmonadicな関数の様に振る舞ってくれて(x&f) yx f yと評価されます。一方(f&x) yと書くとこちらはy f xと評価されます。

この手法を用いているのが((0=3&|),(0=5&|))の部分で、3&|5&|の部分がそうです。|剰余演算子なので、例えば(3&|)103 | 10と評価されて、これはいわゆる10%3であり、評価結果は1になります。

ついでにいうと(0=3&|) xは(演算子は右から評価されるので)3 | xを計算して、その結果と0を比較する(dyadicな=は比較)、いわゆる 0 == (x%3) のような計算です。

monadic fork

これがJ言語で最も面白い機能だと思っていますが、monadicな関数f hとdyadicな関数 gを合成して(fgh) と書くことが出来て、(fgh) y(f y) g (h y) と評価されます。

これも((0=3&|),(0=5&|))の部分で用いられていて、0=3&|f0=5&|hとみて、,gになります。dyadicな,はリストの連結を行うので、例えば ((0=3&|),(0=5&|)) 12のようなことをすると、(0 = (3 | 12)) , (0 = (5 | 12))が評価されて1 , 0となり ((比較演算子=x == yのとき1、x != yのとき0を返します)) 、1 , 0 が評価されて1 0というリストになります

FizzBuzzの作り方

ここまでの解説で((0=3&|),(0=5&|))というmonadicな関数は引数x

  • 3でも5でも割り切れないとき0 0
  • 3で割り切れて5で割り切れないとき1 0
  • 3で割り切れなくて5で割り切れるとき0 1
  • 3でも5でも割り切れるとき1 1

を返すことがわかりました。ここから'FizzBuzz'の文字列を組み立てていきます。核となるのはdyadicな#で、これはcopyと呼ばれる働きをします。大雑把に言って、x # yyx個並んだリストを作成します。それでは(4 4)#((0=3&|),(0=5&|))はどうなるかというと、例えばxが3でのみ割り切れるとき、1 1 1 1 0 0 0 0を返し、xが3でも5でも割り切れるときには1 1 1 1 1 1 1 1を返します。

更にこの結果を使って、(1 1 1 1 0 0 0 0)#'FizzBuzz'のようなことをするとどうなるでしょうか。この場合は先頭4文字は1回繰り返され、後ろ4文字は0回繰り返されるので'Fizz'が結果として返ってきます。同様に(1 1 1 1 1 1 1 1)#'FizzBuzz'の結果は'FizzBuzz'となり、(0 0 0 0 0 0 0 0)#'FizzBuzz'の結果は''になります。実際にはx (f~) yy f xと評価される助動詞~ ((adverbなんで「助動詞」と訳しましたがverbを「関数」とした場合の適切な訳語がわかりませんでした。「マクロ」くらいですか? )) を用いて ('FizzBuzz'#~(4 4#((0=3&|),(0=5&|))) と書いています。

FizzでもBuzzでもFizzBuzzでもない数の扱い

ここまでで3 or 5で割り切れる数についてFizzBuzzFizzBuzzへの変換が出来ましたが、3でも5でも割り切れない数については空文字列が返るだけだったので、ここをなんとかする必要があります。ここで条件分岐を行う演算子を紹介します。それはf`g@.condで、実際には`という関数と@.という関数の合わせ技で、やってることは[f, g][cond]みたいな配列の要素の参照みたいな感じなのですが、いわゆるイディオムというやつなのでこのまま紹介しています。condは0または1に評価されて、condが0のときfが、1のときgが返るので条件分岐みたいになります。

というわけで、condは、「渡された文字列が空文字列かどうか」を判定すればよく、f(すなわち空文字列ではなかったとき)はいわゆる恒等関数、すなわち受け取った文字列をそのまま返すような動作をし、g(空文字列だったとき)はもともとの数値を文字列化したものを返せばよいというわけです。

ただし、今もっているのは''または'Fizz'または'Buzz'または'FizzBuzz'のいずれかであり、もともとの数値が何であったかなどは憶えていません。そこで一つ工夫をします。先程も登場したmonadic forkを用いて(toString関数) (右側の引数が空文字列であれば左側の引数を返し、そうでなければ右側の引数を返す関数) (FizzBuzz化関数) xとします。FizzBuzz化関数のところをそのまま書くとややこしいのでhと書いて、 (":) ( ]`[@.(''-:]) ) hがそれにあたります。][はdyadicにはそれぞれ「右辺を返す」「左辺を返す」ような関数になっていて、''-:]は空文字列''と右辺]の比較になります *1

完成

で、これを展開すると((":)(]`[@.(''-:]))('FizzBuzz'#~(4 4)#((0=3&|),(0=5&|))))となり、省略できる括弧を省略すると":]`[@.(''-:])'FizzBuzz'#~4 4#(0=3&|),0=5&|になります。一見これで完成のようにも見えますが、これ自体は数値1つのみを受け取るmonadicな関数になっているので、最後におまじないのように"0を追加して1 2 3 ...のような数値列を受け取ります。

(":]`[@.(''-:])'FizzBuzz'#~4 4#(0=3&|),0=5&|)"0 

で、あとはこれに引数を>:i.100と渡せばFizzBuzzが出力されます。わかりましたか? 私は全然わかりません。これでもしJ言語に興味をもたれたなら、J言語の公式Wikiページや、手前味噌ではございますが、以下の同人誌にJ言語についての寄稿を行っていますので、そちらをご覧ください。

kosen14s.booth.pm

*1:数値と文字列では"ランク"が違うので使う関数も異なります。このあたりはよくわかってない