続・はじめてのPerl 7章

今日もやっぱりまとめなんです。

7章 サブルーチンへのリファレンス

7.1 名前付きサブルーチンを参照するには
サブルーチンのリファレンス
    コードレフとも呼ばれる

    バックスラッシュ演算子を使い、取得する
        配列やハッシュのリファレンスの取得方法と同じ

        サブルーチン名の前のアンパサンドは必須

        末尾の括弧は不要

        コードレフは、スカラーが使えるすべての場所で使える
            ・スカラー変数
            ・配列の要素
            ・ハッシュの値

コードレフのデリファレンス
    デリファレンスする理由
        サブルーチンを呼び出すため
sub greet {
    my $person = shift;
    print "Hay, $parson\n";
}

# サブルーチンのリファレンス取得
my $ref_to_greet = \&greet;

# リファレンスを使ったサブルーチンの呼び出し
&{ $ref_to_greet }( 'tyamaguc' );

# ブレース内が単純なスカラーなら、ブレースは省略できる
&$ref_to_greet( 'tyamaguc' );

# 矢印記法も使える
$ref_to_greet->( 'tyamaguc' );

# 配列の要素にコードレフを指定
my @array = (
    $ref_to_greet,
);
$array[0]->( 'tyamaguc' );

# ハッシュの値にコードレフを指定
my %hash = (
    'tyamaguc'  =>  $ref_to_greet,
);
while ( my ( $key, $value ) = each %hash ) {
    $value->( $key );
}
7.2 無名サブルーチン
無名サブルーチン
    無名配列や無名ハッシュと同様に、無名サブルーチンも作成できる

    通常のサブルーチンの宣言と似ている
        異なる点
            ・subとブロックの間に、名前を指定しない
            ・無名サブルーチンは文の一部
                末尾にセミコロン、または、式セパレータを置く

    無名サブルーチンの宣言はコードレフを返す
# $greetの値はコードレフ
my $greet = sub {
    my $person = shift;
    print "Hay, $person\n";
};
$greet->( 'tyamaguc' );

# 配列の要素に指定
my @array = (
    sub {
        my $person = shift;
        print "Hay, $person\n";
    },
);
$array[0]->( 'tyamaguc' );

# ハッシュの値に指定
my %hash = (
    'tyamaguc'  =>  sub {
        my $person = shift;
        print "Hay, $person\n";
    },
);
my ( $key, $value ) = each %hash;
$value->( $key );
7.3 コールバック
コールバック
    サブルーチンが、アルゴリズムの特定の位置に達したとき実行される内容

File::Findモジュール
    findサブルーチン
        2つの引数
            ・コードレフ
            ・開始ディレクトリのリスト

        開始ディレクトリから再帰的にディレクトリをたどり、
        見つかったファイルや、ディレクトリを、指定されたコードレフで処理する

        コールバックが実行されたときのカレントディレクトリは、ファイルが見つかったディレクトリ
            ・$_には、カレントディレクトリ内でのアイテムの名前がセットされる
            ・$File::Find::nameは、検索を開始したディレクトリからの相対パスがセットされる
use File::Find;

find(
    sub {
        print "$File::Find::name\n";
    },
    qw( . )
);
=comment
下記のようにコールバックに指定するためだけに、
名前付きサブルーチンを定義するのは馬鹿らしい。
よって上記のように、無名サブルーチンを使う。

sub what_to_do {
    print "$File::Find::name\n";
}

find( \&what_to_do, qw( . ) );
7.4 クロージャ
クロージャ
    宣言時に存在していたすべてのレキシカル変数にアクセスするようなサブルーチン

    Perlでは、スコープの外にあるレキシカル変数にアクセスするサブルーチン

    クロージャ宣言は、クロージャ内でアクセスしている変数の参照カウントを増やす
        つまり、クロージャ内で変数にアクセスすると、
        アクセスされた変数はサブルーチンリファレンスが破棄されるまで、存在することになる

use File::Find;

my $callback;
{
    my $count = 0;  # 参照カウント 1
    $callback = sub {
        print ++$count, ": $File::Find::name\n";
    };    # 参照カウント 2
}
# この時点で、countという名前は破棄されるが、
# 参照カウントは0ではないのでデータは残る

find( $callback, qw( . ) );
7.5 サブルーチンからサブルーチンを返す
use File::Find;

=comment
my $callback;
{
    my $count = 0;  # 参照カウント 1
    $callback = sub {
        print ++$count, ": $File::Find::name\n";
    };    # 参照カウント 2
}
=cut

# 上記のコードは、サブルーチンからコードレフを返すようにした方が便利
sub create_callback {
    my $count = 0;
    return sub {
        print ++$count, ": $File::Find::name\n";
    };
}

my $callback = create_callback();
find( $callback, qw( . ) );

=comment
create_callbackの呼び出し時に、$countの初期化

$countは、create_callbackが制御を戻すとスコープから外れるが、
戻り値である、コードレフからアクセスされているため、
コードレフが破棄されるまで、アクセスできる状態にある
=cut

# create_callbackを2回呼び出せば、2つの異なる$count変数を作れる
my $callback1 = create_callback();
my $callback2 = create_callback();

# それぞれ、別の$countをインクリメントしていく
find( $callback1, qw( . ) );
find( $callback2, qw( ./書籍まとめ ) );


# 見つかったファイルのサイズの合計を取得するには?
#   → 2つコードレフを返せばいい
sub create_coderef {
    my $total_size = 0;
    return (
        sub {
            $total_size += -s if -f;
        },
        sub {
            return $total_size;
        },
    );
}

# 受け取ったコードレフは呼び出すと、同じ$total_sizeに対してアクセスする
my ( $callback3, $get_total_size ) = create_coderef();
find( $callback3, qw( . ) );

my $total_size = &$get_total_size();
print "total size: $total_size\n";
7.6 入力としてのクロージャ変数
# クロージャが閉じ込めることができるのは、
# いずれ、スコープから外れるレキシカル変数のみ
use File::Find;

sub print_bigger_than {
    my $minimum_size = shift;
    return sub {
        print "$File::Find::name\n" if -f and -s >= $minimum_size;
    };
}

my $bigger_than_1024 = print_bigger_than( 1024 );
find( $bigger_than_1024, qw( . ) );
7.7 静的ローカル変数としてのクロージャ変数
# 名前付きサブルーチンでもクロージャになれる
{
    my $count = 0;

    sub add_count {
        $count += shift || 1;
    }

    sub print_count {
        print "$count\n";
    }
}

# 名前付きサブルーチンは、スコープから抜けても破棄されない
add_count( 2 );
add_count( 3 );
add_count( 4 );
print_count();
上記のコードでは、count_add(), print_count() 以外からは$countにアクセスできない
    C言語では、このようなものを静的ローカル変数と呼ぶ

静的変数
    プログラムの一部のサブルーチンからのみアクセスでき、
    サブルーチン呼び出しにより、作成されたり消滅したりせずに、
    プログラムの生涯にわたって存在する変数
add_count( 2 );
add_count( 3 );
print_count();

{
    my $count = 0;

    sub add_count {
        $count += shift || 1;
    }

    sub print_count {
        print "$count\n";
    }
}

add_count( 4 );
print_count();

=comment
実行すると、以下のように出力される
5
4
上記のコードが、思ったとおりに動かないのは理由
    my $count = 0; は2つの動作が含まれるから
        1. コンパイル時の動作
            コンパイル時に、レキシカル変数として宣言される。このときの値はundefとなる

        2. 実行時の動作
            実行時、0を代入する処理が行われる
                上記のコードでは、代入が行われる前にサブルーチンから値の変更が行われている。
                結果、値が上書きされ、0となる
                    add_count( 2 ); により $countは undefから2へ
                    add_count( 3 ); により $countは 2 から 5 へ
                    my $count = 0; により、5 から 0 へ

    ブロックをBEGINブロックに書き換えることで、想定どおりに処理される
add_count( 2 );
add_count( 3 );
print_count();

BEGIN {
    my $count = 0;

    sub add_count {
        $count += shift || 1;
    }

    sub print_count {
        print "$count\n";
    }
}

add_count( 4 );
print_count();

=comment
実行すると、以下のように出力される
5
9
BEGINブロック
    Perlコンパイラは、BEGINブロックを見つけると、
    ブロック内の解析と同時に実行まですませる
        ブロック内の実行で、致命的なエラーが発生しなければ、
        ブロックの後からコンパイルを続ける

    BEGINブロックはプログラムの生涯で1度だけ実行される
        ループやサブルーチンに含まれている場合でも1度のみの実行

感想

知らなかったことが多くて、勉強になった。
Perlまじ面白い!


続・初めてのPerl 改訂版

続・初めてのPerl 改訂版