例えばPythonで階乗を書くと次のようになるだろう。
def fact(x, ini):
if x == 1:
return ini
else:
return fact(x - 1, ini * x)
実行例:>>> fact(3, 1)
6
>>> fact(4, 1)
24
>>>
再帰で書いてるが、その辺はどうでも良い。
このコードを次のようにいたずらしてみる。
def fact(x, cc):
if x == 1:
cc(1)
else:
fact(x - 1, lambda u: cc(u * x))
前のコードと見た目の構造は似ている。ただ、前のコードは初期値(ini)が外部から与えられる整数値だったのに、このバージョンは関数になっている。
従って、再帰部分の第1引数はラムダ式による関数を受け渡された形になっているのだ。
実行例:>>> fact(3, print)
6
>>> fact(4, print)
24
そして、もうちょっとイタズラをしてみようと思う。
save = False
def fact(x, cc):
if x == 1:
global save
save = cc
cc(1)
else:
fact(x - 1, lambda u: cc(u * x))
大域変数saveを用意し、先程のfactで計算脱出前(具体的には引数ccに1が手渡される直前)のccの値、を大域変数saveに代入する。
これを走らせても前やったような結果が表示されるだけで、特に変わりはない。
実行例:>>> fact(3, print)
6
>>>
ところで、「ccの値を大域変数saveに代入する」と簡単に書いたが、ここには一体何が代入されてるのだろう。
C言語みたいなヘナチョコな言語の場合は「値を代入する」と言った時点で、整数なり実数なりの「数値」が保存される、と言う意味である。
ただし、Pythonは関数がファーストクラスオブジェクトのプログラミング言語である。「関数がファーストクラスオブジェクト」とはどういう意味だろうか。つまり、Cと違って、関数を自由に変数として代入したり出来る言語だ、と言う事である。
と言う事は、cc(1)になる「直前の状態」がsaveに代入されてる、って事だ。具体的にはlambda u: cc(u * x)が代入されていて、そしてこのlambda u: cc(u * x)にはfact(3, print)の1までの計算の「その直前までの」情報が全て詰まってるのだ(ヘンな日本語だがそのまんま、である)。
実際、先程の大域変数saveは引数を付けて呼び出す事が出来る。
実行例(続き):>>> fact(3, print)
6
>>> save(1)
6
>>> save(2)
12
>>>
つまり、関数factは階乗を計算する目的で作っているので、fact(3, print)は当然3 × 2 × 1を計算する。ところが、最後の1を計算する「直前」に大域変数saveに1を計算する前の状態が代入される。具体的には3 × 2 ×である。この中途半端な状態が大域変数saveに入ってるのだ。
だからsaveを引数1で呼び出すと6(=3 × 2 × 1)が出力されるし、引数2で呼び出すと12(= 3 × 2 × 2)が出力されるわけだ。saveには中途半端な状態が代入されるが引数を与える事によって最後のピースが埋まって計算が実行されるわけである。
この「計算途中の状態」を継続と呼ぶ。大域変数saveには継続が代入されたわけだ。
なお、ccを引数無しで呼び出すと関数オブジェクト(名)が返ってくる。
>>> save
<function fact.<locals>.<lambda> at 0x7fdb8a38f1f0>
>>>
まぁ、何の役に立つのか分からんが、Pythonでも「継続」と言うものを扱える、と言う話である。