a prime number

http://golf.shinh.org/p.rb?a+prime+number
828180...54321 という大きな素数(なのか?本当に)を出力せよ、という問題。82から1まで改行無しで順に出力すればよい。C言語だと

n=83;main(){for(;--n;)printf("%d",n);}

こんな感じで38Bだ。特に迷うこともなく簡単に書ける。ただ、問題は、topは、これを、37Bで解いてしまった。う〜んどうやったんだ?全然わからん。

また、GolfScriptで以下のように8Bをsubmitし、topだと思っていたら、あっさり6Bで抜かされた。

83,1>-1%

こちらは、なんとかその後6Bに追いついた。『GolfScriptはなかなか奥が深いな』っと、以前は宿敵言語だと思っていたものにはまりつつある、ミイラ取りシンドロームか。
ちなみに、上記を解説すると、GolfScriptはスタック・マシンで、以下を実行する。

83 # 数値83をpush
,  # 配列生成のオペレーター。数値83をpopし、[0 1 ... 82]という配列を生成しpush
1  # 数値1をpush
>  # 配列のスライス。配列と数値1をpopし、配列のインデックス1以降のスライスをpush
-1 # 数値-1をpush
%  # 配列のフィルターリング。配列の各要素のインデックスを、オペランドとして与えられた
   # 数値(この場合は-1)で割り、その剰余が0となった要素のみを集めた配列を生成する。
   # 特に、割る数値が負の場合、生成された配列は、reverseされる。
   # 本ケースの場合、結局、popした配列をreverseしてpushするということになる。
   # -1%は配列や文字列のreverseというイディオムとして覚えておくのが良い。

最終的に、スタックには、[82 81 ... 1]という配列が入っているが、これがそのまま、'['や']'無しに出力され、結果の出力が得られる。
GolfScriptでは、操作対象のデータ型には、数値、文字列、配列、ブロックがある。ブロックは、'{...}'のように中括弧で囲まれていて、中身を実行させることができる関数のようなものだ。例えば、

[1 2 3]{)}/

は、234という結果になる。')'は数値を+1するオペレーター。'/'は、配列の各要素にブロックを作用させその結果をスタックに積んでいくというオペレーターだ。代わりに、前述の'%'を使うと、

[1 2 3]{)}%

結果は、配列にまとめられてからスタックに積まれる。こちらは、いわゆる、mapオペレーターだ。2,3,4と別々の数値としてスタックに積まれるか、[2 3 4]というひとつの配列としてスタックに積まれるかの違いだ。もうひとつ似たものとして'*'がある。こちらは、foldと呼ばれているが、groovyのinject()、jsのreduce()と同等だ。オペランドとなるブロックは、通常、2つのオペランドを受け取りひとつの値をスタックに積むことが要求される。典型的な例としては、以下がある。この場合、'+'は3回呼ばれる。

[1 2 3 4]{+}*
  • 1回目の'+'呼び出しは、1 2が(スタック経由で)渡され、和である3がスタックに積まれる。
  • 2回目の'+'呼び出しは、前回呼び出しの結果である3と次の要素3が渡され、和である6がスタックに積まれる。
  • 3 回目の呼び出しでは、6と4が渡り、和である10がスタックに積まれる。

結果、最終的に、スタックには数値10が残った状態だ。では、これに、1引数のオペレーターを渡したらどうなるのか?

[1 2 3 4]{)}*

結果は、1345 と表示された。3回の')'呼び出しで、渡ってくる最初の値が消費されずにスタックに残ったままとなるだけだ。
なお、オペレーターは、この他にも多数定義されているが、それぞれ、オペランドの型によって動作が異なる。オペレーター・オーバーローディングといういうやつだ。例えば、上記'%'は、数値に作用させると、剰余を求めることになる。')'は、配列に作用させると、配列のいちばん右側の要素を取り出してスタックに追加する。'*'はもちろん数値に作用させると掛け算だ。若干分かりにくいのが、上述の'/'と'%'の結果違いのように、今からあるオペレーターを作用させようとしている対象物がスタックにあるのか、スタックトップの配列にあるのかということだ。つい、配列に入っているものだと思って、最初の要素を'('で取り出そうとすると、実はスタックに展開されていて、スタックトップの数値から1を引くという結果になったりする。

[1 2 3 4]( # ==> [2 3 4] 1
1 2 3 4(   # ==> 1 2 3 3

配列とスタックの変換には、'~'と']'がある。'~'は配列の要素ひとつひとつをスタックに展開して積み、']'は、スタックの内容を配列に収める。

[1 2 3 4]~ # ==> 1 2 3 4
1 2 3 4]   # ==> [1 2 3 4]

あなごるでは、入力は、単一文字列としてスタックに積まれた形で、スクリプトが呼ばれる。入力が複数行の時も単一文字列なので、行に分解するためには、改行でスプリットしてやる必要がある。文字列のスプリットを行うオペレーターは、またしても、'/'と'%'だ。後者、'%'は、空白文字列は削除される点が異なる。改行は、"\n"としても良いが、GolfScriptでは、あらかじめ'n'が改行として定義されている。以下は、いずれも、入力文字列を行に分解し、ひとつの配列に収める。

n/     # 結果の配列には空行も含まれる
"\n"/  # 同上。結果の配列には空行も含まれる
n%     # 結果の配列から空行は取り除かれる
"\n"%  # 同上。結果の配列から空行は取り除かれる

逆に、配列の内容を改行で区切って出力したい場合は、'*'を使う。配列に対する'*'は、joinとして機能するので、セパレーターとして改行を指定すればよい。

[1 2 3 4]n*    # ==> "1\n2\n3\n4"
[1 2 3 4]"\n"* # ==> "1\n2\n3\n4"

まだまだ説明はしたりないが、きりもないので、最後にイディオム的なものをいくつか書いて終わりにする。

~       # ==> 入力が空白/改行で区切られた数字の場合、まず、'~'をやると全て数値化され、
        #     スタックに積まれる。'~'は、文字列のevalだ。
~]      # ==> 上と同様だが、スタックに積んだ数値をさらに配列に収める。
n/      # ==> 入力を行に分解して配列に収める
-1%     # ==> スタックトップの配列/文字列を反転(reverse)する