perlの関数を第一級オブジェクトとして扱う話

  • 24
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

このエントリは、perl advent calender 2014の16日目の記事です。

最近、関数型言語が流行っていますね。ちゃんと勉強しないとなーと思いつつ、あんまりコードをバリバリ書くまでは至ってないです。

で、関数型言語の特徴で、必ず出てくるのが「関数が第一級オブジェクトである」というやつなんですが、実はperlも関数を「第一級オブジェクト」として扱うことができます。

今回は、perlで関数を「第一級オブジェクト」として使う方法とか、その使い方を紹介してみます。

(でもperlは関数型言語ではないですよ)

第一級オブジェクトとは?

実際のコードの前に、まずは「第一級オブジェクト」って何でしょう?

Wikipediaによると、こんな事が書かれています。

第一級オブジェクト(ファーストクラスオブジェクト、first-class object)は、あるプログラミング言語において、たとえば生成、代入、演算、(引数・戻り値としての)受け渡しといったその言語における基本的な操作を制限なしに使用できる対象のことである。

perlの関数を演算することはできないですが、生成、代入、受け渡しはできます。

関数を変数に代入する

無名関数を使う方法

my $coderef = sub {     # subの後ろに関数名が無い -> 無名関数
    my $arg = shift;

    return $arg * 2;
};

print $coderef->(4);    # 8と、表示されます。

関数へのリファレンスを使う方法

sub double {
    my $arg = shift;

    return $arg * 2;
}

my $coderef = \&double;

print $coderef->(4);    # やっぱり8と、表示されます。

どんな時に使う?

では、この関数を変数に代入できる機能、どんな時に使うのでしょう?

よく使われる二つのパターンを紹介します。

callback関数として関数の引数に渡す

色々なところで同じ様なコードが出てくると、それらをまとめて共通関数として切り出すことがよく有ると思いますが、意外と微妙な要件の違いで呼び出し元の要件に応じたロジックが共通関数の中に大量に実装されてしまうことが多々有ります。

下の様なコードを見た事有りませんか?

ここまで極端ではなくとも、呼び出し元に依存するコードががっつり入っている共通関数って意外と多いです。

sub function {
    my $param = shift;
    my $kubun = shift;

    # 共通処理…

    if ( $kubun == 1 ) {
        # 処理区分が1の時の処理
    } elsif ( $kubun == 2 ) {
        # 処理区分が2の時の処理
    } elsif ( $kubun == 3 ) {
        # 処理区分が3の時の処理
    } else {
        # 処理区分が上記以外の時の処理
    }
}

そんな時は個々の要件を切り出して、callback関数として実装してもらう事で適切な抽象度の共通関数を作ることができます。

sub function {
    my $param = shift;
    my $coderef = shift;

    # 共通処理…

    # 呼び出し元の要件に基づく処理は、関数のオブジェクトを渡す。
    if ( $coderef ) {
        $coderef->();
    } else {
        # デフォルトの処理
    }
}

function( $param, sub { # 処理区分1の処理 } ); # 無名関数を渡す
function( $param, \&func_kubun2 );          # 関数リファレンスを渡す

こうやって引数に渡された関数を逆に呼び出す様なコードをcallbackと言います。callbackを上手く使うと、適切な抽象度による疎結合な関数が実現できます。

関数を動的に生成する

関数を返す関数を使うと、もっと面白い使い方ができます。

sub create_html_tag {
    my $tag = shift;

    return sub {
        my $str = shift;

        return '<' . $tag . '>' . $str . '</' . $tag . '>';
    };
}

my $coderef = create_html_tag( 'html' );

print $coderef->('body text'); # <html>body string</html>

この例では、tagを受け取ってhtmlっぽい形式で文字列を囲んでくれる関数を生成しています。

一度作った関数はいつでも使えるので、後は使いたい放題です。

さらっと書いていますが、$tagを引数にとって、そのまま新しく定義した無名関数の中で使っているのが更にポイントです。

関数を定義した時点での、ブロックの外の変数はそのまま参照することができます。これをクロージャと言います。これもよく関数型言語の説明で出てきますね。

実はこれもperlで使えます。

デバッガで見てみる

しかし、動的に生成された関数をデバッガで見る時は注意が必要です。関数名が表示されないので、関数が定義されている行を頼りに追いかける事になります。

Loading DB routines from perl5db.pl version 1.44
Editor support available.

Enter h or 'h h' for help, or 'man perldebug' for more help.

main::(tag.pl:13):  my $coderef = create_html_tag('html');
  DB<1> s
main::create_html_tag(tag.pl:5):        my $tag = shift;
  DB<1> s
main::create_html_tag(tag.pl:10):       };
  DB<1> s
main::(tag.pl:15):  print $coderef->( 'body string' );
  DB<1> s
main::CODE(0x7fbf31988ee8)(tag.pl:8):
8:          my $str = shift;
  DB<1> s
main::CODE(0x7fbf31988ee8)(tag.pl:9):
9:          return '<' . $tag . '>' . $str . '</' . $tag . '>';
  DB<1> s
Debugged program terminated.  Use q to quit or R to restart,
use o inhibit_exit to avoid stopping after program termination,
h q, h R or h o to get additional info.
  DB<1> <html>body string</html>

上記の例で言えば、main::CODE(0x7fbf31988ee8)(tag.pl:8):と出ているので、8行目で定義されている関数、という事は分かります。

シンボリックリファレンスを使って名前をつける

シンボリックリファレンスという機能を使うと、無名関数に名前をつけることができます。

use strict;
use warnings;

sub create_html_tag {
    my $tag = shift;

    my $coderef = sub {
        my $str = shift;
        return '<' . $tag . '>' . $str . '</' . $tag . '>';
    };

    no strict 'refs';
    *{__PACKAGE__ . '::' . $tag } = sub {
        my $str = shift;
        return '<' . $tag . '>' . $str . '</' . $tag . '>';
    };
}

create_html_tag('html');
print html( 'body string' ); # <html>body string</html>

多用すると、これもコードが分かりづらくなってしまうので、どうしても!という箇所に限定して使った方がいいでしょう。

おわりに

perlでも関数は第一級オブジェクトでした、という話でした。

他人のperlのコードを読んでいて一番混乱するのが、関数を代入していたり、生成していたりする箇所です。逆に言うと、この辺を理解すると、他人が書いたperlのコードをより深く理解できる様になると思います。

最近関数型言語を勉強していて、改めてperlの方はどんな事ができたかなーと思い出しながら書いてみました。最後のシンボリックリファレンスを使って、関数を生成する辺りが一番黒魔術っぽいですね!

この辺りの機能のより詳しい解説は、「続・初めてのperl」の"7章 サブルーチンへのリファレンス"に詳しく書かれているので、読んでみて下さい。

明日は、kfly8さんの番です!

この投稿は Perl Advent Calendar 201416日目の記事です。