再帰! スタック! ローカライズ!

再帰を説明するには、まず再帰を定義しなければならない。

とまあ、軽いジョークはおいといて。
恐るべき現実(マイナー言語によるFizzBuzz問題解集) - 永字八法

>なお、NScripterでは再帰はかなりきつい
こんなことを言われると、つい再帰を使いたくなるひねくれ者が来ました(笑)
そんな訳でNScripter再帰FizzBuzzを書いてみましたけど、
FizzBuzz本体より、再帰に必要な土台の方が長い……
http://bugyo.tk/~zick/fizzbuzz.txt

全くこんなことをされると寝る時間が減るじゃないか本当に。と筋違いの文句を言ってみる。
NScripterで、本格的に再帰ができないかどうかを確かめてみる。
そこで、とりあえず動くと言うレベルで組んでみたので、検証のためUPしてみる。
参考にしたのはもちろん、http://bugyo.tk/~zick/fizzbuzz.txtである。

再帰のサンプル。フィボナッチ数列再帰で計算する。

*define
caption "フィボナッチ数列によるスタック実験"
mov %0,100
numalias stack_num,%0:inc %0
numalias stack_cursor_num,%0:inc %0
numalias stack_cursor_str,%0:inc %0
numalias STACK_START,1000
numalias STACK_END,4096
defsub push
defsub pop
defsub push2
defsub pop2
defsub stack_init

numalias fibonacci_result,%0:inc %0
numalias fibonacci_loop,%0:inc %0
numalias fibonacci_n,%0:inc %0
numalias fibonacci_n1,%0:inc %0
numalias fibonacci_n2,%0:inc %0
numalias fibonacci_n3,%0:inc %0
defsub fibonacci1
defsub fibonacci2
game

*fibonacci1
getparam i%fibonacci_result,%fibonacci_n
if %fibonacci_n<1 itoa $fibonacci_n,%fibonacci_n:add $fibonacci_n,"はnの数値として不適当です。":mesbox $fibonacci_n,"error!":end
if %fibonacci_n<3 mov %%fibonacci_result,1:return
mov %fibonacci_n1,1
mov %fibonacci_n2,1
for %fibonacci_loop=3 to %fibonacci_n
mov %fibonacci_n3,%fibonacci_n1+%fibonacci_n2
mov %fibonacci_n1,%fibonacci_n2
mov %fibonacci_n2,%fibonacci_n3
next
mov %%fibonacci_result,%fibonacci_n2
return

*fibonacci2
push %fibonacci_n
push %fibonacci_n1
push %fibonacci_n2
getparam i%fibonacci_result,%fibonacci_n
if %fibonacci_n<1 itoa $fibonacci_n,%fibonacci_n:add $fibonacci_n,"はnの数値として不適当です。":mesbox $fibonacci_n,"error!":end
mov %fibonacci_n1,0
mov %fibonacci_n2,0
if %fibonacci_n<3 mov %%fibonacci_result,1:jumpf
fibonacci2 %%fibonacci_result,%fibonacci_n-2
mov %fibonacci_n1,%%fibonacci_result
fibonacci2 %%fibonacci_result,%fibonacci_n-1
mov %fibonacci_n2,%%fibonacci_result
mov %%fibonacci_result,%fibonacci_n1+%fibonacci_n2
~
pop %fibonacci_n2
pop %fibonacci_n1
pop %fibonacci_n
return

*stack_init
mov %stack_cursor_num,STACK_START
mov %stack_cursor_str,STACK_START
return

*push
if %stack_cursor_num>STACK_END mesbox "stack overflow.","error!":end
getparam i%stack_num
mov %%stack_cursor_num,%%stack_num
inc %stack_cursor_num
return

*pop
notif %stack_cursor_num>STACK_START mesbox "stack underflow.","error!":end
getparam i%stack_num
dec %stack_cursor_num
mov %%stack_num,%%stack_cursor_num
return

*push2
if %stack_cursor_str>STACK_END mesbox "stack overflow.","error!":end
getparam s%stack_num
mov $%stack_cursor_str,$%stack_num
inc %stack_cursor_str
return

*pop2
notif %stack_cursor_str>STACK_START mesbox "stack underflow.","error!":end
getparam s%stack_num
dec %stack_cursor_str
mov $%stack_num,$%stack_cursor_str
return

*start
saveoff
stack_init

for %0=1 to 15
fibonacci2 %1,%0
第%0項は%1
next
click
end

解説

フィボナッチ数列を計算するのに、二つ(内部的には三つ)の命令を自作した。
どちらも二つの引数を取り、第一引数は結果を受け取る変数、第二引数はフィボナッチ数列の項数を指定する。

fibonacci1 a(n),n

こんな風な指定の仕方をする訳だ。
スクリプトを置き換えました。

fibonacci1
再帰とか使わないでフラットに計算する。素直に組んで見た例で、何も問題はない。ただし、n=1から馬鹿正直に計算するので当然ながらnが大きくなればなるほど実行時間はかかる。
fibonacci2
再帰を使って計算するバージョン。内部的にはfibonacci3を呼び出してるだけ。そしてfibonacci1よりも時間がかかるのは分かりきってることだが。

:fibonacci3:fibonacci2の中で使われているいわゆる本体。自分を呼び出す再帰構造を持っている。
fibonacci2とfibonacci3の関係が、なんだかPrologの再帰定義を彷彿とさせるな。つまりこれが、当然の帰結なんだろうか。

発展

まあ、フィボナッチ数列を扱うのは再帰定義的な処理の一例が欲しかっただけで、階乗でも同じことだったとは思うが、フィボナッチ数列の方がメモリー食うので負荷実験にはなると思った。
まあその、再帰をするには変数の局所化が必要で、NScripterでそれができるかどうか、方向を指し示すことはできたと思う。
zickさん、刺激的なブツをありがとうございました!