Perlで継続渡し
色々な言語で継続渡しを書いてみよう企画。今回はPerlで。
#! /usr/bin/perl use strict; sub fib_cps { my ($n, $k) = @_; if (($n == 1) || ($n == 2)) { $k->(1); } else { &fib_cps($n - 1, sub { my $v1 = shift @_; my $k = $k; &fib_cps($n - 2, sub { my $v2 = shift @_; $k->($v1 + $v2); }) }); } } sub main { for my $i (1..9) { printf "Fibonacci(%d)= %d\n", $i, &fib_cps($i, sub { @_ }); } } &main;
Perlは慣れているしクロージャを使うコードもよく見かけるから、今回は楽勝だと思っていたんだけど思わぬところでハマった。それがmy $k = $k;の一文。一見意味がないように見えるが、これがないと正しく動かない。Perlにはlocalとmyの2種類の局所変数があって、たぶん1つ目のクロージャの中で$kを使っていないと暗黙的にlocalとして扱われてしまうためだろう。これってバグに極めて近い仕様じゃないかと思うんだけど…。それ以外は特に難しいこともなく。問題はCで書く場合だよなあ。やり方は何パターンか思いつくけどどれもエレガントとはほど遠い。まあCで書く時点で泥臭くなるのは必然ではあるか。
他の言語と比べると、Perlでは以下のような特徴がある(改めて見るとかなり個性的な言語だ)
- returnが不要
これはちょっとLispっぽい - ぶら下がりif文を許さない
閉じ括弧が開き括弧と同じインデントになるので、あまりLispっぽくは書けない - myとlocalの2種類の局所変数がある
それぞれレキシカルスコープとダイナミックスコープになる。他の言語(特にコンパイル方式の言語)ではレキシカルスコープが普通。SchemeやCommon Lispのletはレキシカルスコープだけどダイナミックスコープのfluid-letもある。Emacs Lispのletはダイナミックスコープ - 変数の種類によって$や@を付ける($_と@_は別の変数)
- $kにコードのリファレンスが入っているとき、$k->()でコードを呼び出せる
- 関数呼び出しは先頭に&を付けて表す(付けなくていい場合もある)
関数呼び出しとコードリファレンスの呼び出しで形式が違うのがちょっとスマートじゃないかも - 仮引数はない
まとめてリストとして@_で渡されるので、必要に応じて自分で分解する - 関数のプロトタイプ宣言はできるけどあまり使われない
プロトタイプ宣言を含めると、例えばsub fib_cps($$) { ... のようになる