LoginSignup
44

Perl のグローバル変数やレキシカル変数について

Last updated at Posted at 2016-04-19

Perl にはいくつかの変数宣言があります。

  • my
  • our
  • local
  • state

my による変数宣言は多くの書籍で解説されていておなじみのものですが、それ以外の変数宣言の意味がイマイチわからないという声も聞きます。というわけで、ざっとまとめてみました。

use strict が無かった時代

モダンな Perl では、use strict を書いた上で、使用する変数は my を使って宣言をするということが常識となりました。

とはいえ太古の昔は use strict もなく、変数も宣言することなく使用していました。いわゆるグローバル変数だけの世界です。

#!/usr/bin/perl

# 宣言無く代入
$foo = "Hello";

まさに原始的なシェルスクリプトの世界に近いですが、今では好ましい書き方とはされません。スコープを狭めたり、初出の変数であることを明示したりといったことを my で行うことが好ましいとされています。Perl も例に漏れず、プログラミング言語で定石とされる、グローバル変数はなるべく使わないという原則です。Bash シェルスクリプトでも set -u または set -o nounset というオプションがあるくらいです。

Perl におけるグローバル変数とは何なのでしょう。一つの表現方法で言えば、グローバル変数はパッケージに紐付いた変数といえるでしょう。

グローバル変数はパッケージに紐付いた変数:パッケージ修飾変数と our

Perl ではプログラムの部品を別ファイルに切り出して外部から読み込んで使う仕組みがいくつかあります。その一つがパッケージです。

Sample1.pm
package Sample1;

1;

こういう骨格のファイルを Sample1.pm という名前で保存すると、Sample1 というパッケージになります。末尾の 1; はお約束です(正確にはこのファイルを読み込んだプログラムが受け取る真値)。歴史が深い Perl なので、こういうのもあるんです(許してあげてください)。

Perl 5.14 以降だと、パッケージのブロック宣言もできるようになりました。

Sample1.pm
package Sample1 {
    ...
}

1;

ここでは、まだまだモジュールの書かれ方として現役の、ブロックではない古い方で解説していきます。

この中でそのまま変数を宣言すればグローバル変数になります。

Sample1.pm
package Sample1;

$config = "設定値"; # グローバル変数

1;

とはいえ、通常は use strict するので、この方法でグローバル変数を作ることはできません。ここで登場するのが our です。

Sample1.pm
package Sample1;

use strict;

our $config = "設定値"; # グローバル変数

1;

use strict 宣言をした時にグローバル変数を作る簡単な方法が our です。

use strict 宣言をしても、パッケージ修飾が明示的にされた変数、つまり変数に :: という文字列が入っている場合は use strict のチェック対象外になり、それはグローバル変数になります。

Sample1.pm
package Sample1;

use strict;

$Sample1::config = "設定値"; # グローバル変数

1;

この例も上記2つと同様です。ただ、パッケージ名を「二度書き」するのはスマートではないですよね。そういうことを踏まえて用意されているのが our です。

パッケージ名を明示した変数はグローバル変数ということは、グローバル変数はパッケージに紐付いた変数ではないかと思ったら、Perl のグローバル変数はだいたい理解できたと思ってよいでしょう。Perl のプログラムの中には色々な論理構造がありますが、最も外側にあるものはパッケージなのです。パッケージ宣言をしない書き捨てのプログラムも、main というパッケージに所属しています。

Perl では __PACKAGE__ と書くと、現在のパッケージ名に展開されます。

foo.pl
#!/usr/bin/perl

use strict;
use warnings;

print "現在のパッケージ名は" . __PACKAGE__ . "です\n";
$ perl foo.pl
現在のパッケージ名はmainです

先ほど作成した Sample1.pm と、書き捨てプログラム foo.pl を同じディレクトリに置くことで、foo.pl から use を使って Sample1.pm を呼ぶことができます。

foo.pl
#!/usr/bin/perl

use strict;
use warnings;

use Sample1;

print "Sample1 は " . $Sample1::config . " と設定されています\n";

$Sample1::config = "別の設定値"; # 取得だけでなく設定もできる
  • Smaple1.pm を 同じディレクトリに置いて use Sample1; できるのは perl -le 'print for @INC' の出力に . だけの行がある場合です。perl-5.26 以降からはこの . が無いため、同様には行かない場合があります。そのような場合は perl -I. foo.pl などとして実行するか、foo.pl で use Sample1; する前の行で use lib "."; などとして下さい。

グローバル変数が無作法であることが常識となった時代では学ぶ機会も少なくなりましたが、パッケージで宣言されたグローバル変数は、別のパッケージからも参照できることが重要です。昔はパッケージの設定をグローバル変数で持って、使用者に外から書き換えてもらうことが良く行われていました。

グローバル変数で設定を持つと書くと、オブジェクト指向が当たり前な人達には「異なる複数の設定を使いまわしたいときどうするの?」と思われるかもしれません。

ここで登場するのがグローバル変数の局所化を行う local です。

グローバル変数の局所化:local

グローバル変数の局所化という聞き慣れない単語がでてきました。

Sample1 パッケージには、$Sample1::config 変数に入れた設定値によって動作を変える some_task というサブルーチンが定義されているとしましょう。この some_task サブルーチンは様々な場所から様々な設定値で使われるとしましょう。

foo.pl
#!/usr/bin/perl

use strict;
use warnings;

use Sample1;

$Sample1::config = "今回はこの設定値";
Sample1::some_task();

今回はこの設定値にして some_task を実行すれば、ここでのタスクは完了です。しかし $Sample1::config の値を戻しておかないと、some_task を呼ぼうとした別の場所で予期せぬことが起こるかもしれません。以前の値は戻しておくべきです。

foo.pl
#!/usr/bin/perl

use strict;
use warnings;

use Sample1;

$config_backup = $Sample1:config;
$Sample1::config = "今回はこの設定値";
Sample1::some_task();
$Sample1::config = $config_backup;

目的を果たしたとはいえ、上のコードは面倒だし保守性も低いコードです。

そこで Perl 4 の時代に用意されたのが、グローバル変数の局所化を行う local という宣言です。局所化といってもグローバル変数であることには変わりなく、ブロックの中で local 宣言されると、ブロックの外に出たところでグローバル変数の値を元に戻してくれるのです。

上記と同等のものを書いてみましょう。

foo.pl
#!/usr/bin/perl

use strict;
use warnings;

use Sample1;

{ # 無名ブロック
    local $Sample1::config = "今回はこの設定値";
    Sample1::some_task();
}

今回は無名ブロックで書きましたが、制御構造やサブルーチン宣言などハッシュに関わらない中括弧はだいたいブロックを構成します。パッケージ宣言も、Perl 5.14 以降のブロック記法はブロックを作ります。

上記のように宣言と代入を同時に行うとわからないですが、local で宣言だけしたグローバル変数は未定義値となります

foo.pl
#!/usr/bin/perl

use strict;
use warnings;

use Sample1;

{ # 無名ブロック
    local $Sample1::config; #宣言のみ
    if ( !defined $Sample1::config ) {
        print "設定は未定義です\n";
    }
    Sample1::some_task();
}

とりあえず設定値は同じままグローバル変数の局所化だけしておいて、あとで変数をいじっていく場合には以下のような書き方をします。初見ではドキッとする書き方ですが、上記で説明した宣言と代入の話が分かっていれば理解できるのではないでしょうか。

foo.pl
#!/usr/bin/perl

use strict;
use warnings;

use Sample1;

{ # 無名ブロック
    # とりあえず今までの設定値のまま局所化。代入演算子が右結合なので(右から左に評価されるので)大丈夫
    local $Sample1::config = $Sample1::config; # 宣言と代入
    if ( !defined $Sample1::config ) {
        print "設定は未定義です\n";
    }
    Sample1::some_task();
}

local とか見慣れないし、ダサくても手作業でやってもいいんですよね」というのも大体は等価です。ただ、some_task が中で呼んでいるサブルーチンがさらに some_task を呼んでいて…といった再帰呼び出し的な場合に意図しない結果が起こる場合があります。とても複雑な場合には local での局所化でも奇妙なことが起こるのですが、そういう煩わしたを経て、Perl 5 の use strictmy、そしてオブジェクト指向パッケージに結実するのだと思っていただけると良いでしょう。

Perl 4 の頃は、レキシカル変数宣言の代わりとして local を使っていました。範囲外での参照は use strict 前夜なので警告などはでませんが、「スコープ外」で以前使っていた値を参照されてしまうということを回避する目的です(使っていないグローバル変数が参照されたということで未定義値を得るだけになります)。

とはいえタイプミスであったりといったありがちなミスを手軽に補足したい。そういう構文(lexical: レキシカル)レベルでチェックすることができるようになったのが、Perl 5 の use strictmy といった道具なのです。

パッケージとの紐付けが無くなったレキシカル変数:mystate

上記で繰り返されたのが**「グローバル変数=パッケージとの紐付いた変数」**というものでした。

Perl 5 から、パッケージと紐付かない変数であるレキシカル変数を宣言するための my が導入されました。

「グローバル変数の対義語はローカル変数だろう」というのはごもっともなのですが、グローバル変数の局所化で local という単語を使ってしまったこともあり、ローカルつまり局所化という単語はそもそも不適切なんですね。

プログラミング言語には「宣言された変数が有効な範囲」という意味の「スコープ」という単語があります。このスコープという概念で「スコープの中で定義したものは外からは見えないよね」というスコープを「静的スコープ」とか「レキシカルスコープ」と呼び、そういうスコープ内で有効な変数のことを「レキシカル変数」と呼ぶのです。

(この辺りはプログラミング言語の専門家の方々ならもっと詳しいと思いますので、専門書であったり近くの専門家の方に話を聞いてみると良いでしょう)

先ほどの Sample1 パッケージですが、この設定値を my で宣言してしまった途端、パッケージとの紐付けが無くなってしまい、外から参照することができなくなってしまいます。

Sample1.pm
package Sample1;

use strict;

my $config = "設定値"; # レキシカル変数となって、`use` したプログラムが `$Sample1::config` としても参照できない

1;

パッケージとの紐付けが無くなってしまったわけで、外からいくらパッケージ修飾子を付けても参照はできないのです。my で宣言したことでスコープ内でのみ参照可能なレキシカル変数となってしまい、パッケージと紐付いた変数=グローバル変数ではなくなってしまったのです。

local の場合と同様ですが、my のレキシカルスコープも大体ハッシュに由来しない中括弧で作られます。

「上記の例には中括弧は無いのでは?」という質問は当然浮かびますが、最終的にはパッケージのすぐ外側にあるファイルという枠が作る暗黙のスコープ、つまりファイルスコープに属します。Perl 5.14 以降で導入された package Sample1 { ... } というブロック構文の書き方では、中括弧がブロックを作ります。

  • 2016/05/18 追記: Perl 5.14 以前のフラットな構文でもパッケージが暗黙のブロックを作ると以前の版で書いていましたが、実際はブロック構文でない場合にはファイルスコープなどの上位にあるスコープに対するレキシカル変数となります。パッケージ宣言を包含するスコープのレキシカル変数であっても、パッケージとの紐付けが無くなってしまったことに変わりはありません。

こういう前提があり、パッケージのすぐ冒頭で my 宣言したレキシカル変数であっても、パッケージを use した呼び出し元から書き換えることはできません。これはパッケージが侵食されないという保証を手軽に受けることができるという意味で重要です。パッケージの冒頭で my 宣言されたレキシカル変数は、パッケージ全体のグローバル変数のような使い方ができるものの、それはパッケージの外から読み書きすることはできず、実際は多くの場合パッケージのすぐ外側にあるファイルスコープに属するレキシカル変数となって、パッケージからはクロージャ変数のように振る舞うことになります。

パッケージと紐付く変数=グローバル変数、パッケージと紐付かない変数=何らかのスコープ内でしか参照できない変数=レキシカル変数、という理解はできたでしょうか。

state について説明していませんでした。これは Perl 5.10 で導入された特殊なレキシカル変数の一種です。

サブルーチンに状態を持たせる特殊なレキシカル変数:state

ベタですが、呼び出し回数を記録しているサブルーチンを作ってみましょう。

counter.pl
#!/usr/bin/perl

use strict;
use warnings;

my $count_value = 0;

print count() . "\n"; # 1
print count() . "\n"; # 2
print count() . "\n"; # 3

sub count {
    $count_value++;
    return $count_value;
}

count サブルーチンは、自分の状態を記録するために自分より上のスコープにレキシカル変数(グローバル変数でも可)が必要になっています。「サブルーチンの状態をサブルーチンの外に置くのはスマートではない」とはいえ、count サブルーチンの中で my 宣言をしても都度初期化されてしまうので、意図した結果にならないのはすぐに解ります。

counter.pl
#!/usr/bin/perl

use strict;
use warnings;

print count() . "\n"; # 1
print count() . "\n"; # 1 うまくいかない
print count() . "\n"; # 1 うまくいかない

sub count {
    my $count_value = 0
    $count_value++;
    return $count_value;
}

このファイルが長くなった場合、$count_value を下でいじってしまうことを防ぐのであれば、count サブルーチンの定義自体を狭いスコープでくくってしまう方法があります。いわゆるクロージャと呼ばれるものに近いです。

counter.pl
#!/usr/bin/perl

use strict;
use warnings;

print count() . "\n"; # 1
print count() . "\n"; # 2
print count() . "\n"; # 3

{ # 無名ブロック:スコープを作る
    my $count_value = 0;
    sub count {
        $count_value++;
        return $count_value;
    }
}

$count_value は宣言されている行より下に書かれていれば問題なく、さらに sub 宣言はどんな深いスコープで行われたとしてもパッケージに紐付きます。「グローバルサブルーチン」と書くと誤解を招きそうですが、サブルーチンはパッケージ内のどこで宣言してもスコープの制限は受けずにパッケージに紐付くので、上記の書き方は意図した結果を生みます。もっとも、通常は混乱を招かないよう、パッケージの一番浅いところでサブルーチンを宣言します(混乱する例は後述)。

だいぶ保守性の高いプログラムになりましたが、やはりスッキリしません。

Perl 5.8 時代はこれが精一杯だったのですが、Perl 5.10 でサブルーチンの状態を保持する特殊なレキシカル変数宣言である state が導入されました。上記の counter.pl は以下のように書けます。

counter.pl
#!/usr/bin/perl

use strict;
use warnings;
use feature 'state'; # state を使う場合に必要

print count() . "\n"; # 1
print count() . "\n"; # 2
print count() . "\n"; # 3

sub count {
    state $count_value = 0;
    $count_value++;
    return $count_value;
}

statemy と等価ですが、プログラム実行時に一度評価されたらその行の二度目の評価はされません。前述の Perl 5.8 で精一杯の例のように、サブルーチンの外側を囲うスコープがあるとみなして、そこで my 宣言されたかのような振る舞いをします。結果的に、サブルーチンの状態を保持するレキシカル変数(他の場所から無理やり壊されることがない変数)をスマートな書き方で実現することができます。

もっとも「カウンターを初期化するときはどうするのか」といった要望もあるでしょうし、こういう実装はちょっと複雑になったらオブジェクトが状態を保持してメソッドとして実装したほうが良いでしょう。また、関数型言語と呼ばれるプログラミング言語のジャンルでは「参照透過性」といって、このように状態や副作用を持った関数自体を様々な厄介事をかかえる存在として忌避する立場もあります。

あくまで手軽に、非常に小さなプログラム構造の中でピンポイントで必要という用途のときの便利な書き方というのが state の位置付けです。

付記として、先ほどの説明にあった「サブルーチンをパッケージの一番浅いところで宣言しない場合に混乱する例」を挙げます。例えば、サブルーチンの宣言の中でサブルーチンを宣言することは文法上問題がありませんが、2つのサブルーチンは結果から観察すると「パッケージの一番浅いところに巻き上げられたかのようになる」ので、同じレキシカル変数を複雑な方法で共有しているとエラーが起こります。

wrong.pl
#!/usr/bin/perl

use strict;
use warnings;

foo(); # Variable "$count" will not stay shared at wrong.pl line 11.

sub foo {
  my $count = 1;
  sub bar {
    print "count = $count\n";
  }
  $count++;
}

もっとも、こんなコードを書いたら Perl の敏腕プログラマの方々に怒られてしまいますね。とはいえ、使用しているフレームワークの仕組み的にサブルーチン内(メソッド内)でコードの動的生成をしている場合などは、この罠にひっかかる可能性も無いとは言えないでしょう(最も著名な例は mod_perl による CGI エミューレート環境 Apache::Registry や ModPerl::Registry およびその派生モジュールでしょう)。

注:昔ながらのサブルーチン宣言ではないサブルーチンリファレンスであったり、Perl 5.18 から実験的に導入されたレキシカルサブルーチンはまた別の概念です。

真のグローバル変数

冒頭で挙げた4種類の変数宣言を解説しましたが、分かったでしょうか。パッケージに紐付く変数=グローバル変数、ということを念頭に置けば、Perl の変数の理解はそう難しくはないでしょう。

私が良く真のグローバル変数と呼んでいるものがあります(私が以前どこかで聞いた単語ですが、ほぼ私独自の呼び方だと思います)。例えば、Perl のプログラマであればお馴染みの**$_ などの1文字変数がそれ**です。

$_ は特殊な変数で、どんな場所で $_ と書いても同じ $_ なのです。ちょっとわかりづらいかな…。もっと具体的に説明すれば、どんなパッケージ内で $_ と書いてもそれは main パッケージに所属する $main::_ なのです

Foo というパッケージ内で何度 $_ と書いてもそれは読み書き両方において $main::_ のことですし、Moge パッケージの中であっても $Moge::_ と不自然な過ぎるくらいパッケージ名を明示して書くことで初めて Moge パッケージ内に $Moge::_ というグローバル変数ができるくらいです。

もうこれ以上ないグローバル変数ですね。Perl の $_ はとても普遍的な(英単語 it のような)変数です。です。このような振る舞いは結果的に便利だったりします。

とはいえ、何でも $_ だと時にぶつかってしまいます。わかりやすく変数へ代入すればいいのですが、理解して使うのであれば Perl の省略記法は非常に便利です。その場合、真のグローバル変数である $_local で局所化することができます。

# <> で標準入力から1行を $_ に入れる
while (<>) {
    # url= で URL が書かれていればそれを $1 で取り出し、無ければ パス
    /^url=(https?:\s+?)/ or next; 

    # URL からレスポンスボディをもらう get 関数が定義されていてそれを使う
    {
        local $_ = get($1); 

        # local $_ で $_ にレスポンスボディを確保したので、<> で呼んだ行を退避させつつ s/// で =~ を省ける
        s/one/ein/g;
        s/two/zwei/g;
        s/three/drie/g;
        ...
    }
}

チームでプログラミングをする場合なら、よほどの事情が無ければ名前をつけた変数に入れた方がいいですが、自分一人で楽をしたいという場合には役立つこともあります(またはそういうソースコードを見た時に驚かない知識です)。

ここでは $_ を題材にしましたが、他の1文字変数などの真のグローバル変数は上記同様 local による局所化が可能です。

例えば行区切りの一文字変数 $/ は有名で、通常は $/ = "\n" です。つまり「一行という概念は "\n" という文字によって区切られているので "\n" の直後でカットしていく」という意味です。

Perl の readline または <> という記法は「指定されたファイルハンドルから一行を取ってくる」という意味ですが、$/ を未定義にすることで、一行がファイルハンドルで読める全体の意味になり、丸飲みが可能となります。つまり

my $buffer = '';
while (<$fh>) {
    $buffer .= $_;
}

というコードは

$/ = undef; # または undef $/;
my $buffer = <$fh>;

と書くことができます。しかし $/ を戻しておかないと、全く別の場所で <> をしている場所で意図しないことが起こるかもしれません。これを回避するために、真のグローバル変数 $/ を局所化してやるのです。スコープを作り、文の一番最後の評価値を返す do ブロックを使って、こんなイディオムにまで縮めることができます。

my $buffer = do { local $/; <$fh> };

local で局所化宣言をした場合、$/ は未定義値となってしまい、そして do のブロックが終わったところで $/ は以前の値に戻るので、他の場所に悪影響を与えることがありません。

local は何でも局所化できる

my our state は、スカラー変数($)、配列(@)、ハッシュ(%)といった変数の宣言ですが、local はこれ以外の、例えば配列やハッシュの枝葉といったものの局所化もできます。


# some_task は %config ハッシュを読んで動作を決定するらしいんだけど、一時的に $config{escape} の値だけ変えたい
{
    local $config{escape} = 0;
    some_task();
}

オブジェクト指向プログラミングが行き届いたモダンな世界ではあまり用途は無いかもしれませんが、古い外部モジュールを使うときに活用できるテクニックではあります。

レキシカル $_ は非推奨

真のグローバル変数 $_ は非常に多く使われるので、レキシカル化されたら便利なのではと Perl 5.10 で my $_ といった書き方ができるようになりました。

しかし、多くのモジュールが $_ は真のグローバル変数であると想定していることなどから混乱は大きく、Perl 5.18 で非推奨となりました。もっとも、レキシカル $_ は現場ではほぼ使われていないと言えるもので、無くなっても問題はないでしょう。Perl 5.24 で廃止勧告が行われるという話もあります。

エイリアス変数という概念

一時的に設定される値を破壊すると、原本まで変わってしまう変数があります。

alias.pl
#!/usr/bin/perl

use strict;
use warnings;

use Data::Dumper;

my @numbers = (1, 2, 5, 6);

for my $number (@numbers) {
  $number++;
  print "number=$number\n";
}

print Dumper(\@numbers);

受け取った数字を 1 だけ増分して表示したいだけだったのですが、これを実行すると…

$ perl alias.pl
number=2
number=3
number=6
number=7
$VAR1 = [
          2,
          3,
          6,
          7
        ];

$VAR1 部分が @numbers 配列の最終的な状態のダンプですが、元の配列の要素の値まで変更されてしまっていることがわかります。

ここで for の一時変数とした $number ですが、配列 @numbers の値を順次コピーしているのではなく、$number の中を覗き込むと @numbers の所定の要素そのものを覗き込んでいるのと同一となります。このような変数のことを別名変数またはエイリアス変数と呼ばれます。エイリアス変数を破壊的な操作であるインクリメント演算子 ++ で破壊してしまったことで、原本である @numbers 配列の中身まで書き換えてしまったのです。

再度、明示的な代入によって変数のコピーを取れば問題ありません。

alias2.pl
#!/usr/bin/perl

use strict;
use warnings;

use Data::Dumper;

my @numbers = (1, 2, 5, 6);

for my $number (@numbers) {
  my $nubmer2 = $number;
  $number2++;
  print "number=$number2\n";
}

print Dumper(\@numbers);

Perl には ++ であったり s/// であったりといった、変数の中の数値や文字列をオンデマンドに変更(破壊)してしまうコマンドがあり、そういうものが注意の対象となります。

このエイリアス変数が問題となるのは主に for map そして grep です。

square.pl
#!/usr/bin/perl

use strict;
use warnings;

use Data::Dumper;

my @numbers = (1, 2, 5, 6);

my @squares = map { $_ **= 2; $_ } @numbers;

print "squares => @squares\n";
print "numbers => @numbers\n"; # 破壊されてしまう
$ perl squares.pl
squares => 1 4 25 36
numbers => 1 4 25 36

この場合、map の中の $_@numbers の各要素のエイリアス変数となっています。

インクリメント演算子 ++ での破壊例は滅多にないとしても、map などで受け取ったデータを s/// してから返そうというときに意図せず破壊してしまうケースは少なくありません。

もっとも、これを応用すれば、長い変数名 $too_long_long_logn_variable のエイリアス変数として $_local なしで使うことができます。これにはエイリアス変数を設定する for を利用します。

$too_long_long_long_variable = get("http://www.yahoo.com/"); # LWP::Simple などより
# =~ を省いてエイリアス変数 $_ 経由で $too_long_long_long_variable を複数回置換する
for ($too_long_long_long_variable) {
    s/yahoo/___YAHOO!___/ig;
    s/hello/good night/ig;
    ...
}

エイリアス変数は、便利だから導入されているというよりは、Perl 内部的に無駄なコピーを発生させないためのものという意味合いのほうが大きいようです。

実体がコピーされないというのはリファレンスと似ていますが、リファレンスがシンボリックリンクのようなものだとすれば、エイリアス変数はハードリンクのようなものに近いでしょう。

参考文献

Perl 標準添付の perldoc を読むと、このあたりの話が緻密に書かれています。日本語でも perldoc.jp で有志による和訳が公開されています。

  • perlsub サブルーチンについて
  • perlmod モジュールとパッケージについて
  • perlvar グローバル変数など変数全般について

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
44