「Perlのエラーメッセージのコーナー」、第2回を飛ばして第3回。
「"my" variable %s can't be in a package」である。
Present
ブロック{}の中で変数を局所化したいとき、Perl 4ではlocalを使っていたがPerl 5ではmyを使う。
my変数はパッケージに属さないレキシカル変数であって、スコープはeval関数の引数、ブロック、あるいはファイルの中である。

localも変数を局所化するように見えるが、実際には変数の値をスタックに覚えておいて、必要がなくなったらもとに戻す。

myとlocalの違いは、サブルーチン先で見えなくなるmyと、見えるlocalの違いである。
myの方が実感に近い。

#! /bin/perl
#
# myLocal.pl -- myとlocalの違い

#use strict; # $sをどのパッケージに属さなくても出来るようにstrictを無効にする
use warnings;
use 5.010;

$s = 1;
warn $s; # 1 と表示される

{
  my $s = 3;
  warn $s; # 3 と表示される
  &sub_001;
   # warn $s; でまず 1 と表示される(ブロックの外側の$sが見える)
   # $s = 100;
   # warn $s; で 100 と表示される

warn $s; # 3 と表示される(ブロックの内側の$sが見える)
}

warn $s; # 100 と表示される(さっきサブルーチンsub_001で変えられたから)

{
local $s = 5;
warn $s; # 5 と表示される
&sub_002;
   # warn $s; でまず 5 と表示される(ブロックの内側の$sが見える。ここがmyとの違い)
   # $s = 200;
   # warn $s; で 200 と表示される
warn $s; # 200 と表示される(さっきサブルーチンsub_002で変えられたから)
}

warn $s; # 100 と表示される(localの前に戻る)

sub sub_001 {
warn $s;
$s = 100;
warn $s;
}

sub sub_002 {
warn $s;
$s = 200;
warn $s;
}

今日のお題のエラーメッセージは、次のようなプログラムで起きる。

#! /bin/perl
#
# error003.pl

use strict;
use warnings;
use 5.010;

$main::i = 3;

say "\$main::iの値の発表(1回目)=>", $main::i;

{
  my $main::i = 5;
  say "\$main::iの値の発表(2回目)=>", $main::i;
}

say "\$main::iの値の発表(3回目)=>", $main::i;

実行するとこうなる。

C:\Perl\perl>error003.pl
"my" variable $main::i can't be in a package at C:\Perl\perl\error003.pl line 14, near "my $main::i "
Execution of C:\Perl\perl\error003.pl aborted due to compilation errors.

SVMの文型である。

(主語S)"my" variable $main::i
(翻訳)"my" 変数 $main::i は

(動詞V)can't be
(翻訳)~にはなれない(~では存在できない)

(修飾語M)in a package
(翻訳)パッケージの中に

よって全文の訳としては「"my" 変数 $main::i はパッケージの中には存在できない」という意味である。

$main::iはmainパッケージに属するパッケージ変数であり、myと矛盾する。

この場合はmyを使わないでlocalを使う。

#! /bin/perl
#
# error003fixed.pl

use strict;
use warnings;
use 5.010;

$main::i = 3;

say "\$main::iの値の発表(1回目)=>", $main::i;

{
  my $main::i = 5;
  say "\$main::iの値の発表(2回目)=>", $main::i;
}

say "\$main::iの値の発表(3回目)=>", $main::i;

実行してみる。

C:\Perl\perl>error003fixed.pl
$main::iの値の発普i1回目)=>3
$main::iの値の発普i2回目)=>5
$main::iの値の発普i3回目)=>3

Perl 5の時代になってもいまだにlocalをよく見るのは、$/のような特殊変数を一時的に変えるときだ。

$/は<ファイルハンドル>からの読み込みにおいてどこを区切りにするかが入っているもので、普通は\nが入っている。これをundefにすると1回<ファイルハンドル>から読み込んだだけで全ファイルの中身がスカラーで返る。
これをslurpモードという。
slurpという動詞はとろろ芋のようなものをずるずるっと丸呑みにすることらしい。
「すすりこみモード」とでも訳すのだろうか。
「一気読みモード」の方がいいか。
このとき、他の<ファイルハンドル>読み込みに影響を与えるとズイマーなので、ブロック{}で囲んでlocal宣言する。

#! /bin/perl -w
#
# globalSlurpHTMLreplace.pl -- HTML の<b>>を<strong>に一気読みモードで検索置換
#
# sample file name: globalSlurpHTMLreplace.pl

use File::Find;

find(\&fileProc, '.');

sub fileProc {
  my $fname = $_; # あとで $_ が変わるからここで取っておく

  return unless -f $_;
  return unless /\.html?$/;

  {
   my $in;
   open $in, $_;
   local $/; # ★注目★ localを使う
   undef $/; # 改行記号を無効にする
   $_ = $in; # ファイル全体の中身を $_ に一気に読み込む
   close $in; # もう IN には用がない
  }

  1 while s|(.*?)|$1|s;

  open TMP, ">$fname.temptemp"; # こんなファイル名のファイルはないとする
  print TMP;
  close TMP;

  rename "$fname.temptemp", $fname
   or warn "cannot rename $fname because $! \n";
}

上のコードでlocalをmyに変えると以下のように怒られる。

Can't use global $/ in "my" at C:\Perl\perl\globalSlurpHTMLreplace.pl line 19, near "my $/"

グローバル変数$/を"my"の中に入れてはいけない、ということだ。

ちなみに、『Perlベストプラクティス』では$/をいじるかわりにPerl6::Slurpが推奨されている。


このエントリーをはてなブックマークに追加