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種類の局所変数がある
    それぞれレキシカルスコープとダイナミックスコープになる。他の言語(特にコンパイル方式の言語)ではレキシカルスコープが普通。SchemeCommon Lispのletはレキシカルスコープだけどダイナミックスコープのfluid-letもある。Emacs Lispのletはダイナミックスコープ
  • 変数の種類によって$や@を付ける($_と@_は別の変数)
  • $kにコードのリファレンスが入っているとき、$k->()でコードを呼び出せる
  • 関数呼び出しは先頭に&を付けて表す(付けなくていい場合もある)
    関数呼び出しとコードリファレンスの呼び出しで形式が違うのがちょっとスマートじゃないかも
  • 仮引数はない
    まとめてリストとして@_で渡されるので、必要に応じて自分で分解する
  • 関数のプロトタイプ宣言はできるけどあまり使われない
    プロトタイプ宣言を含めると、例えばsub fib_cps($$) { ... のようになる