スカラーリファレンスの用途

  • 8
    いいね
  • 3
    コメント

配列リファレンスやハッシュリファレンスは、「配列の配列」であったりといった多階層のデータ構造を作る上でよく用いられます。しかしながら、スカラーリファレンスについては一緒にその存在を教わるものの、その用途については解説されないことが多いです。

ここではスカラーリファレンスの用途を挙げていきたいと思います。この他にも用途を知っている方はコメントなどでフォローいただけると幸いです。

巨大なデータの受け渡し

以下のようなプログラムがあるとします。

my $str2 = greeting($str1);
...

sub greeting {
    my $arg = shift;
    $arg =~ s/おはよう/おやすみ/g;
    ...
    return $arg;
}

greeting というサブルーチンに文字列変数 $str1 を渡して、何らかの処理をした後に結果を返すというもの。

ただ、greeting サブルーチンに渡す $str1 が何百MBも何GBもする任意のテキストファイルから丸呑みしたデータだとしたら、ここでサブルーチンに値を渡すことで発生するコピーは無視できません。

サブルーチンに文字列を値渡しをすることで発生するコピーが無視できないと考えられる場合、これを値渡しすなわちスカラーリファレンスにすることで軽減を図ることがあります。

greeting(\$str1); # スカラーリファレンスを渡す

...

sub greeting {
    my $arg = shift;
    die if ref $arg ne 'SCALAR';
    $$arg =~ s/おはよう/おやすみ/g;
    ...
    # return $arg; # 大本をいじっているので返り値を返す必要は必ずしもない
}

または、サブルーチン側がスカラーとスカラーリファレンスどちらでも受け取れるバージョンにすることもできます。

my $str2 = greeting($arg); # $arg はスカラーの場合とスカラーリファレンスの場合どちらでも

...

sub greeting {
    my $arg = shift;
    my $sref = ref $arg eq 'SCALAR' ? $arg : \$arg;
    $$sref =~ s/おはよう/おやすみ/g;
    ...
    # 返り値を受けろうとしている場合のみデリファレンスして return する
    return $$sref if wantarray;
}

丸呑みした巨大な(場合も考えられる)テキストとしての文字列に対して複数回置換処理を行うサブルーチンなどは、この手法を適用して効果が出やすいはずです。

ある種の型として

ファイルパスを渡すとそのファイルの中身を読んで適切な変更を加えた結果を返すサブルーチン get_content があったとしましょう。

sub get_content {
    my $filepath = shift;
    open my $fh, '<', $filepath or die "can not open. $!";
    my $content = do { local $/; <$fh>; };
    $content =~ s/foo/bar/g;
    close $fh;
}

この例は小さなものですが、定義が大きくなったときに「ファイルパスではなくファイルの中身も渡せるようにしたい」という仕様追加を考えたとします。

  • ファイルパス自体が文字列なので、文字列の扱いに困る
  • 渡されたものがファイルハンドルであれば ref $arg eq 'GLOB' || UNIVERSAL::isa($ref, "IO::Handle") のような検査ができるんだけど…
  • ハッシュリファレンスで { content => $content } として渡すのもいいけれど、もうちょっとシンプルなのがいい

この場合、ファイルパスは文字列で渡しつつ、既に取得しているファイルの中身である巨大な文字列を渡すときはそのスカラーリファレンスで渡すというやり方もあります。前述の通り、巨大になる可能性がある文字列を渡す時にはそもそもスカラーリファレンスの方が都合が良いというのもあります。

sub get_content {
    my $arg = shift;
    my $content;
    if ( !ref $arg ) {
        # スカラーの場合はファイルパス
        my $filepath = $arg;
        open my $fh, '<', $filepath or die "can not open. $!";
        $content = do { local $/; <$fh>; };
        close $fh;
    } elsif ( ref $arg eq 'SCALAR' ) {
        # スカラーリファレンスの場合はファイルの内容
        $content = $$arg;
    }
    $content =~ s/foo/bar/g;
    return $content if wantarray;
}

Perl コアの例としては、入力セパレータ文字列を保持している特殊変数 $/ でしょう(通常これは "\n" です)。 perlvar より:

$/ に整数、整数を含むスカラ、整数に変換できるスカラのいずれかへの リファレンスをセットすると、行を読む代わりにレコードを読もうとします; この場合、最大レコードサイズはリファレンス先の整数値の文字数となります。 つまり:

    local $/ = \32768; # or \"32768", or \$var_containing_32768
    open my $fh, "<", $myfile or die $!;
    local $_ = <$fh>;

これは $fh から 32768 文字を超えないようにレコードを読み込みます。 もしレコード指向のファイルを読み込まない場合(あるいは OS がレコード指向 ファイルを持たない場合)、読み込み毎にデータのチャンク全部を取り込みます。 もしレコードがセットしたレコードサイズより大きい場合、 レコードの部分を取り込みます。 レコードサイズを 0 以下にセットしようとするのは廃止予定で、 $/ に値 "undef" が設定され、(残りの)ファイル全体を読み込むことになります。

上記例では、バイト数を示す整数値をそのまま渡すことができるものの、それ自体がセパレータ文字でないことを明確化するため、バイト数の整数の数値をリファレンス化しています。

無名配列リファレンス [...] や無名ハッシュリファレンス {...} のように、無名スカラーリファレンスという概念もあります。

# 無名スカラーリファレンス
my $greeting_scalarref1 = \"Hello";
# 以下も同様
my $greeting_string = "Hello";
my $greeting_scalarref2 = \$greeting_string;

# 無名数値リファレンス(無名スカラーリファレンスの一種)
my $integerref = \32768;

# デリファンレスする場合には $ で
print "Greeting is " . $$greeting_scalarref1 . "\n";

オブジェクトのデータとして

通常のPerlでのオブジェクトは、ハッシュリファレンスにパッケージを bless するでしょう。

ただ bless する対象は何らかのリファレンスであればいいので、配列リファレンスやスカラーリファレンスを選ぶことができます。

そのスカラーリファレンスが指す文字列(もしくはバイト列)情報以外にプロパティ的値や状態を保持しないのであれば、ハッシュリファレンスではなくスカラーリファレンスを bless する対象に選ぶことで、省資源化が図れます。

著名なモジュールでは URI がこの例として挙げられます

とはいえ bless 対象としてハッシュリファレンスではなくスカラーリファレンスを選んだ場合の資源の節約具合は、通常の使われ方であればほぼ分からないレベルだと思います。