Perl/変数、データ構造
ここではPerlの変数とデータ構造についての基本的な事項を学びます。応用的な事項についてはリファレンスで触れます。
変数
[編集]Perlでは、変数は「データを格納する領域(オブジェクト)に付けられた名前」です。 同じオブジェクトを2つ以上の変数が指し示すこともありますし、配列の要素のように、単体では名前がないオブジェクトもあります。 変数を用いることにより、データをより柔軟に扱うことができます。
変数の利用の形態は大きく分けて宣言、代入、参照の3種です。
データ型
[編集]Perlには3つの組み込みデータ型があります。
- スカラー
-
- 文字列
- サイズは問わず、使用可能なメモリ量に制限されます
- 数値
-
- 整数
- 処理系固有のネイティブな整数
- 浮動小数点数
- 処理系固有のネイティブな整数
- 文字列で表現された数値
- 上記の整数・浮動小数点数をPerlの数値リテラルで表現したもの(”NaN” ”Inf” なども含まれます)
- 参照
- これについては リファレンス で説明します
接頭辞
[編集]変数のデータ型は、接頭辞( sigil ; シジル)によって区別します。
変数の種類と接頭辞 種類 接頭辞 説明 スカラー $ 数値や文字列などの値を1つだけ保持します。 配列 @ 複数の値を、順序付きで保持します。 ハッシュ % 複数の値を、重複しないキーに結びつけ保持します。 リファレンス $ スカラー、配列あるいはハッシュのメモリ上のアドレスを保持します。 リファレンスは、特殊なスカラーなので接頭辞は同じ $
です。
データ型の種類
[編集]スカラー
[編集]スカラーは、内部に構造をもたない「ただ一つ」の値を保持できるデータ型で、接頭辞は、$
(ドルマーク;Scalar の S)です。
- 例
#!/usr/bin/perl use v5.12; use warnings; my $name = "太郎";# レキシカル変数 $name を "太郎" で初期化 my $age = 30; # レキシカル変数 $age を 30 で初期化 say $name; say $age;
- 実行結果
太郎 30
- スカラー変数の接頭辞は、
$
(ドルマーク;Scalar の S)です。 - 数学では「スカラー」の対義語は「ベクトル」ですが、Perlでは、「リスト」が「スカラー」の対義語です。
- 数学的にはしっくりきませんが、文字列もスカラーです。
- 変数が示している値が数値か文字列かは、値側にある情報「型」により決まります。
- 上記コードでは、「name」という名称のスカラー変数 $name と、「age」という名称のスカラー変数 $age があります。
- スカラー変数 $name は、文字列 "太郎" を保持し、
- スカラー変数 $age は、数値(整数) 30 を保持します。
- このように、一つの変数は、単一の値を保持できます。
スカラーリテラルの表記
[編集]- 文字列リテラル
”ABC and Z”
や'ABC and Z'
のように、”
(ダブルクォーテーションマーク)や’
(シングルクォーテーションマーク)で囲みます。- 数値
”
や’
囲む必要はありませんが、囲んでも実行時に数値として解釈されます。また、NaN や Inf のような特殊な値は、”
や’
囲む必要があります。
変数名を含む識別子の規約
[編集]- 識別子の先頭は、英大文字・英小文字あるいは ’_’
- 識別子の2文字目以降は、英大文字・英小文字・'0'..'9' あるいは ’_’
- 識別子の長さは、1文字以上251文字以下
- 識別子の正規表現
[A-Za-z_][A-Za-z_0-9]{0,250}
- 不正な識別子の例
123age new-word フラッグ
- 先頭に数字は使えない
- ’-’は変数名に使えない
- 論外
演算子主導の型強制
[編集]Perlは、演算子式を評価するに当たり、演算子ごとにオペランドの型を強制的に変換します。
- 例
#!/usr/bin/perl use v5.12; use warnings; my $age = 30; say __LINE__ . ": $age"; $age = $age . "1"; say __LINE__ . ": $age"; $age = $age + 1; say __LINE__ . ": $age"; $age = $age x 3; say __LINE__ . ": $age"; $age = $age / 302; say __LINE__ . ": $age";
- 実行結果
5: 30 6: 301 7: 302 8: 302302302 9: 1001001
- このように、演算子によってオペランドが数値と解釈されたり文字列と解釈されたりします。
- 逆に言うと、演算子を見ればオペランド(と式の値)の型がわかります。
次のプログラムは、等価な代入演算子による実装です。
- 例
#!/usr/bin/perl use v5.12; use warnings; my $age = 30; say __LINE__ . ": $age"; $age .= "1"; say __LINE__ . ": $age"; $age += 1; say __LINE__ . ": $age"; $age x= 3; say __LINE__ . ": $age"; $age /= 302; say __LINE__ . ": $age";
- 実行結果
5: 30 6: 301 7: 302 8: 302302302 9: 1001001
例外的に、インクリメント演算子は数値にも文字列にも作用します。
- 例
#!/usr/bin/perl use v5.12; use warnings; my $n = 3; say __LINE__ . ": $n"; $n++; say __LINE__ . ": $n"; $n++; say __LINE__ . ": $n"; my $s = "K";say __LINE__ . ": $s"; $s++; say __LINE__ . ": $s"; $s++; say __LINE__ . ": $s"; $s = "BY"; say __LINE__ . ": $s"; $s++; say __LINE__ . ": $s"; $s++; say __LINE__ . ": $s"; $s++; say __LINE__ . ": $s";
- 実行結果
5: 3 6: 4 7: 5 9: K 10: L 11: M 13: BY 14: BZ 15: CA 16: CB
- 5-7 の数値のインクリメントは、違和感ありませんが
- 9-11 の1文字の文字列のインクリメントは、キャラクターコードを増している???と思いますが、
- 14-16 の2文字の文字列のインクリメントでは、桁上りをしています!
このような文字列をオペランドに取ったときのインクリメントをマジカルインクリメントと呼びます。
Perlでは、スカラー変数に整数・浮動小数点数・文字列やリファレンスの間で暗黙の変換が働くからです。
- レキシカルスコープのスカラー変数の宣言
my $変数名 = 初期値;
- 例えば、print 関数の引数に整数が渡されれば、自動的に十進数の文字列に変換されます。
- このように、演算子・関数・サブルーチンやメソッドが適宜変換します。
- これは、Perlが参考にしたAWKと同じ特徴です。
また、Perlで「データ型」というと
- スカラー
- 配列
- ハッシュ
- (コード)
- (ファイルハンドル)
- (フォーマット)
配列
[編集]配列はスカラーの集合で、整数のインデックスでそれぞれの要素スカラーを参照できるデータ型で、接頭辞は、@
(アットマーク;array の a)です。
- 例
#!/usr/bin/perl use v5.12; use warnings; my @ary = ("太郎", 30); # 配列変数 @ary を、リスト ("太郎" 30) で初期化 say __LINE__ . ": $ary[0]"; # 配列変数 @ary の 0 番目の要素を参照 say __LINE__ . ": $ary[1]"; # 配列変数 @ary の 1 番目の要素を参照 say __LINE__ . ": $ary[-1]"; # 配列変数 @ary の -1 番目(最後)の要素を参照 say __LINE__ . ": $ary[-2]"; # 配列変数 @ary の -1 番目(最後から 2 番目)の要素を参照 say @ary; # そのまま文字列化すると、要素を文字列化したものを区切り文字なく連結 { local $, = ";" ; say @ary; } # {} でスコープを切って local で区切り文字グローバル変数 $, をローカライズしたので say @ary; # スコープを抜けると元通り say __LINE__ . ": \@ary = @ary"; # 文字列中で展開すると、区切り文字は1文字の空白 say __LINE__ . ": \@ary = @{[@ary]}";# ベビーカー演算子 @{[式]} でも同じ $ary[1] = 25; # 1番めの要素に 25 を代入 say __LINE__ . ": \@ary = @ary"; # 置換わる $ary[1]++; # インクリメント say __LINE__ . ": \@ary = @ary"; # 増える $ary[1] = "ABC"; # 1番めの要素に "ABC" を代入 say __LINE__ . ": \@ary = @ary"; # 置換わる $ary[1]++; # インクリメント say __LINE__ . ": \@ary = @ary"; # 増える! push @ary, "XYZ"; # push は配列の末尾に追加 say __LINE__ . ": \@ary = @ary"; # 増える(違う意味で) unshift @ary, "UVW"; # unshift は配列の先頭に追加 say __LINE__ . ": \@ary = @ary"; # 増える(また違う意味で) my $x = shift @ary; # shift は先頭要素の取出し say __LINE__ . ": $x"; # 戻値は取出した値 say __LINE__ . ": \@ary = @ary"; # 先頭がなくなった my $y = pop @ary; # pop は末尾要素の取出し say $y; # 戻値は取出した値 say __LINE__ . ": \@ary = @ary"; # 末尾がなくなった my $z = @ary; # スカラ変数へ配列変数を代入すると say __LINE__ . ": \$z = $z"; # 要素数が入る push @ary, qw(A B C D); # リストをpush(qw/STRING/の例) say __LINE__ . ": \@ary = @ary"; # 展開されて追加される foreach my $el(@ary) { # foreach ループは先頭から順位要素にブロックを適用 say __LINE__ . ": \$el = $el"; } foreach (@ary) { # ループ変数を宣言しなくても $_ で要素を参照できる say __LINE__ . ": \$_ = $_"; } say __LINE__ . ": \$_ = $_" foreach (@ary); # 同じ意味
- 実行結果
7: 太郎 8: 30 9: 30 10: 太郎 太郎30 太郎;30 太郎30 16: @ary = 太郎 30 17: @ary = 太郎 30 20: @ary = 太郎 25 23: @ary = 太郎 26 26: @ary = 太郎 ABC 29: @ary = 太郎 ABD 32: @ary = 太郎 ABD XYZ 35: @ary = UVW 太郎 ABD XYZ 38: UVW 39: @ary = 太郎 ABD XYZ XYZ 43: @ary = 太郎 ABD 46: $z = 2 49: @ary = 太郎 ABD A B C D 52: $el = 太郎 52: $el = ABD 52: $el = A 52: $el = B 52: $el = C 52: $el = D 56: $_ = 太郎 56: $_ = ABD 56: $_ = A 56: $_ = B 56: $_ = C 56: $_ = D 59: $_ = 太郎 59: $_ = ABD 59: $_ = A 59: $_ = B 59: $_ = C 59: $_ = D
- 接頭辞は、
@
(アットマーク;array の a)です。 - 配列の要素にはスカラーが入り、1つの配列に文字列や数値やリファレンスが混在することができます。
配列変数の代入の意味論
[編集]- 例
#!/usr/bin/perl use v5.30.0; use warnings; my @x = 1..10; say __LINE__ . ":\@x --> @x"; my @y = @x; say __LINE__ . ":\@y --> @y"; $y[$_] *= 2 foreach (0..$#y); say __LINE__ . ":\@x --> @x"; say __LINE__ . ":\@y --> @y";
- 実行結果
6:@x --> 1 2 3 4 5 6 7 8 9 10 9:@y --> 1 2 3 4 5 6 7 8 9 10 12:@x --> 1 2 3 4 5 6 7 8 9 10 13:@y --> 2 4 6 8 10 12 14 16 18 20
- Perlで、配列変数同士のコピーは、全ての要素の一対一の複製です。
当たり前のようですが、動的型付けの言語の中では、意外と少数派です。
- Rubyの例
x = Array(1..10) puts "#{__LINE__}:x --> #{x}" y = x puts "#{__LINE__}:y --> #{y}" y.map!{|i| i * 2} puts "#{__LINE__}:x --> #{x}" puts "#{__LINE__}:y --> #{y}"
- 実行結果
2:x --> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 5:y --> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 8:x --> [2, 4, 6, 8, 10, 12, 14, 16, 18, 20] 9:y --> [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
- Rubyでは、配列の代入は別名を作ることになり、同じオブジェクトを示します。
- このため、片方の変数をつかい配列要素の値を書換えると、他方から参照しても書換わります。
- 複製が欲しい場合は、Array$clone をつかいます。
- JavaScriptの例
let x = [...Array(10)].map((_, i) => i + 1) console.log(`x --> ${x}`) let y = x console.log(`y --> ${y}`) for (let i = 0, len = y.length; i < len; i++) y[i] *= 2 console.log(`x' --> ${x}`) console.log(`y' --> ${y}`)
- 実行結果
x --> 1,2,3,4,5,6,7,8,9,10 y --> 1,2,3,4,5,6,7,8,9,10 x' --> 2,4,6,8,10,12,14,16,18,20 y' --> 2,4,6,8,10,12,14,16,18,20
- JavaScript、配列の代入は別名を作ることになり、同じオブジェクトを示します。
- やはり、片方の変数をつかい配列要素の値を書換えると、他方から参照しても書換わります。
- 複製が欲しい場合は、Array$concat を無引数でつかいます。
このほか、Pythonも配列の代入は別名作成です。少数派ですが、PHPがPerlと同じです。
配列リテラル
[編集]配列にリテラルはありません。 配列リテラル風に見えるものはリストです。
リスト
[編集]リストは、スカラー・配列やハッシュのようなデータ型ではなく書式です。
配列変数の初期化にリストが使われるので紛らわしいのですが、
- 「配列変数」はあり「リスト変数」はありません。
- 「リストを返す関数」とはいいますが、「配列を返す関数」とはいいません。
- 「リストコンテキスト」とはいいますが、配列コンテキストとはいいません。
リストは、複数の変数を一括宣言したり、多値代入につかわれます。
配列とリストは似通っていますが区別する必要があります。
- 例
use v5.12; # v5.12 は use strict の機能を含んでいます。 use warnings; my @name = ("太郎", "次郎"); my ($name1, $name2) = @name; say $name[1]; say "$name[1]"; say @name; say "@name"; my ($x, $y) = (123, 999); say "$x $y"
- 実行結果
次郎 次郎 太郎次郎 太郎 次郎 123 999
なお、
my @name = ("太郎", "次郎"); print ">$name<\n";
- 実行結果
><
- エラーにはなりませんが、
$name
は表示されません。
つぎに
use v5.12; # v5.12 は use strict の機能を含んでいます。 use warnings; my @name = ("太郎", "次郎"); print ">$name<\n";
- エラー表示
Global symbol "$name" requires explicit package name (did you forget to declare "my $name"?) at Main.pl line 5. Execution of Main.pl aborted due to compilation errors.
- と2つのプラグマを補うと、エラーを発見してくれます。
use v5.12; use warnings;
- の2つは必ず指定しましょう。
use v5.12;
のv5.12
は、このバージョンからstrictがディフォルト化されたので使いましたが、2010/04/16 リリースなのでコレより古いリリースを使っている可能性は2022年11月現在ないと思います。また、use v5.12;
すれば say が使えるようになります。
qw演算子を使って
my @name = qw(太郎 次郎); say $name[1];
- 実行結果
次郎
qw演算子は、空白で区切った文字のシーケンスを受け取り、区切られた文字列を要素とするリストを返します。
- リストの文字列化
my @ary = (10,4,2); say @ary ;
- 実行結果
1042
- このように、リストの要素は文字列化された後、区切り文字なしに連結されます。
区切り文字
[編集]10,4,2
のように区切り文字をつけて出力したい場合、組込み関数 join を使うか、特殊変数$,を使います。
- join と $,
use v5.12; # v5.12 は use strict の機能を含んでいます。 use warnings; my @ary = (10, 4, 2, 8); say join "," , @ary; { local $, = "'"; say @ary; } say @ary;
- 実行結果
10,4,2,8 10'4'2'8 10428
- $, はグローバルな特殊変数なので、一旦値を変えると自動的には元に戻らないので、コードブロックでスコープを切ってlocal宣言することで値を復帰できるようにします。
sort関数による並べ換え
[編集]sort関数を使うと、配列を並べ替える事ができます。 並べ替えることができますが、数値の配列であっても辞書順に並べ替えてしまいます。
- 辞書順にソート
use v5.12; # v5.12 は use strict の機能を含んでいます。 use warnings; my @ary = (10, 4, 2, 8); say "0: @ary"; say "1: @{[ sort @ary ]}"; say "2: @{[ sort { $a <=> $b } @ary ]}"; say "3: @{[ sort { $b <=> $a } @ary ]}"; say "4: @{[ sort { $b cmp $a } @ary ]}"; say "5: @{[ sort { $a cmp $b } @ary ]}";
- 実行結果
0: 10 4 2 8 1: 10 2 4 8 2: 2 4 8 10 3: 10 8 4 2 4: 8 4 2 10 5: 10 2 4 8
1:
が辞書順になる理由は、比較が文字列として行なわれるからです。2:
の{$a <=> $b}
が追加部分ですが、$a?<=>
? $b が今までと気配が違います。- sort関数はPerl4の頃からあり、いまなら@_を使うところですが、過去のコードとの後方互換性からこの形式で残っています。
- プロトタイプ($$)を使う方法も用意されましたが、無名関数が使えないので冗長な表現になり、名前空間汚染という意味では $a $b といい勝負です。
- $aと$bは、パッケージ内に所属しているグローバル変数です(sub のように @_ で引数を受け取るのではなくグローバル変数を使っています)。
<=>
は宇宙船演算子といわれる二項演算子で、大なり(1)・等しい(0)・小なり(-1)を返します。- sort は並べ替えのアルゴリズムの「比較」の部分にこのブロックを使います。
- $aと$bは、このような出自であり use strict の「グローバル変数が使われています」のチェックをすり抜けてしまいます。
- $aと$bは、sort 以外では使ってはいけません。
- $aと$bは、sort 以外では使ってはいけません。
3:
の{$b <=> $a}
が変更部分です。左右の項を入れ替えたので逆順にソートされます。4:
の{$b cmp $a}
が変更部分です。比較演算子を文字列比較演算子としたので、辞書逆順にソートされます。5:
の{$a cmp $b}
が変更部分です。左右の項を入れ替えました。これがコードブロックを渡さなかったときのディフォルト動作です。
具体的には、Perlの組み込み関数sort()がデフォルトで文字列ソートを行うことが問題の原因となりました。
sort()は数値でも一旦文字列に変換してから並べ替えを行います。そのため、Unix時間が10桁(1000000000秒)になった2001年9月9日前後で、この並べ替えがおかしくなってしまったのです。
例えば、以下のようなソートを行うと:
my @times = (999999999, 1000000000); my @sorted = sort @times; say "@{[ @sorted ]}" #=> 1000000000 999999999
結果は 1000000000 999999999
となり、本来の時系列とは逆転してしまいます。
これは、文字列として "1000000000" と "999999999" を比較すると、先頭の"1"が"9"より小さいため、この順番になってしまうためです。
この問題に対処するには、sort()に数値ソートの引数を渡す必要がありました:
my @times = (999999999, 1000000000); my @sorted = sort {$a <=> $b} @times; say "@{[ @sorted ]}" #=> 999999999 1000000000
このように、Perlの文字列優先のデフォルト動作と、開発者がUnix時間の10桁化を意識していなかったことが、この問題の発生につながりました。
幸いPerlプログラマーの多くはこの問題に気づき、適切な回避策を講じることができました。しかし、この問題は潜在的な不具合につながる可能性があり、Perlプログラマに日時処理の注意を促す良い教訓となりました。
ちなみにEpochからの通算秒が8桁から9桁に変わったのは 1973年3月3日、Perlが誕生する1987年より前です。
10桁から11桁に変わるのは 2286年11月20日です。スライス
[編集]スライスは、配列やハッシュの部分集合へのアクセス方法を提供します。
- スライスの例
use v5.12; # v5.12 は use strict の機能を含んでいます。 use warnings; my @ary = map { $_ * 10 } 0..9; my %hash; $hash{$_} = uc $_ foreach "a".."f"; print <<EOS; \@ary --> @ary \@ary[1,4] --> @ary[1,4] \@ary[1..4] --> @ary[1..4] \@ary[0,1,4..6] --> @ary[0,1,4..6] \@ary[9,0] --> @ary[9,0] \%hash --> @{[ map {"$_=>$hash{$_},"} sort keys %hash ]} \@hash{"a","b"} --> @{[ @hash{"a","b"} ]} \@hash{"d".."f"} --> @{[ @hash{"d".."f"} ]} \@hash{"f","a"} --> @{[ @hash{"f", "a"} ]} \@hash{qw(b e e f)} --> @{[ @hash{qw(b e e f)} ]} \@hash{"f","a"} --> @{[ @hash{"f","a"} ]} EOS say __LINE__ . ": \@ary --> @ary"; @ary[3..5] = 5..7; say __LINE__ . ": \@ary --> @ary"; @ary[5..9] = 5..100; say __LINE__ . ": \@ary --> @ary"; say __LINE__ . qq(: \%hash --> @{[ map {"$_=>$hash{$_},"} sort keys %hash ]}); @hash{"a".."c"} = "AA".."AC"; say __LINE__ . qq(: \%hash --> @{[ map {"$_=>$hash{$_},"} sort keys %hash ]}); delete @hash{"b", "e"}; say __LINE__ . qq(: \%hash --> @{[ map {"$_=>$hash{$_},"} sort keys %hash ]});
- 実行結果
@ary --> 0 10 20 30 40 50 60 70 80 90 @ary[1,4] --> 10 40 @ary[1..4] --> 10 20 30 40 @ary[0,1,4..6] --> 0 10 40 50 60 @ary[9,0] --> 90 0 %hash --> a=>A, b=>B, c=>C, d=>D, e=>E, f=>F, @hash{"a","b"} --> A B @hash{"d".."f"} --> D E F @hash{"f","a"} --> F A @hash{qw(b e e f)} --> B E E F @hash{"f","a"} --> F A 24: @ary --> 0 10 20 30 40 50 60 70 80 90 26: @ary --> 0 10 20 5 6 7 60 70 80 90 28: @ary --> 0 10 20 5 6 5 6 7 8 9 30: %hash --> a=>A, b=>B, c=>C, d=>D, e=>E, f=>F, 32: %hash --> a=>AA, b=>AB, c=>AC, d=>D, e=>E, f=>F, 34: %hash --> a=>AA, c=>AC, d=>D, f=>F,
- 配列の場合は、
@ 配列変数 [ リスト ]
の形式で、リストの要素を添字として対応した値リストがかえります。 - ハッシュの場合は、
@ ハッシュ変数 { リスト }
の形式で、リストの要素をキーとして対応した値のリストがかえります。 - 配列もハッシュも、スライスは左辺値のリストで、スライスを左辺にリストを代入すると、配列やハッシュの内容を書き換えることができます。
- スライスの引数のリストは、
,
で区切った値、..
で示した範囲(マジカルインクリメントも含む)、qw//、関数の戻値など様々なバリエーションがありえるので、強力な表現力を持ちますが、同時にパズル的に難解なコードも書けることを意味しています。
ハッシュ
[編集]ハッシュは、キーとなる文字列とスカラーの値がペアの集合のデータ型です。ハッシュは配列とは違って、順序は一定でないことが保証されます。
ハッシュ変数の接頭辞は %
です。
- 構文
%ハッシュ変数 = ( "キー1" => 値1, "キー2" => 値2, : : "キーn" => 値n, );
- キーが、Perlの識別子として有効ならば
- 構文
%ハッシュ変数 = ( キー1 => 値1, キー2 => 値2, : : キーn => 値n, );
- と書けます。
- キーを、Perlの識別子として有効にすれば
my %myHash = ("Key1" => 3, "Key2" => 4); say $myHash{"Key1"};
- は
my %myHash = (Key1 => 3, Key2 => 4); say $myHash{Key1};
- と書くことができます。
- コード例
#!/usr/bin/perl use v5.12; use warnings; my %hash = ( Tom => 18, Joe => 16, ); say __LINE__ . qq(: \$hash{Tom} -> $hash{Tom}); say __LINE__ . qq(: \$hash{Joe} -> $hash{Joe}); ##say __LINE__ . qq(: \$hash{Sam} -> $hash{Sam}); $hash{Tom}++; say __LINE__ . qq(: \$hash{Tom} -> $hash{Tom}); $hash{Joe}++; say __LINE__ . qq(: \$hash{Joe} -> $hash{Joe}); say __LINE__ . qq(: \%hash -> %hash); say __LINE__ . qq(: \@{[%hash]} -> @{[%hash]}); $hash{Sam} = 0; say __LINE__ . qq(: \@{[%hash]} -> @{[%hash]}); say __LINE__ . qq(: \@{[keys %hash]} -> @{[keys %hash]}); say __LINE__ . qq(: \@{[values %hash]} -> @{[values %hash]}); say __LINE__ . qq!: \@{[map { "\$_:\$hash{\$_}" } keys %hash]} -> @{[map { "$_:$hash{$_}" } keys %hash]}!; foreach my $k(keys %hash) { $hash{$k}++; } say __LINE__ . qq!: \@{[map { "\$_:\$hash{\$_}" } keys %hash]} -> @{[map { "$_:$hash{$_}" } keys %hash]}!; foreach (keys %hash) { $hash{$_}++; } say __LINE__ . qq!: \@{[map { "\$_:\$hash{\$_}" } keys %hash]} -> @{[map { "$_:$hash{$_}" } keys %hash]}!; $hash{$_}++ foreach (keys %hash); say __LINE__ . qq!: \@{[map { "\$_:\$hash{\$_}" } keys %hash]} -> @{[map { "$_:$hash{$_}" } keys %hash]}!;
- 実行結果
10: $hash{Tom} -> 18 11: $hash{Joe} -> 16 15: $hash{Tom} -> 19 17: $hash{Joe} -> 17 19: %hash -> %hash 20: @{[%hash]} -> Joe 17 Tom 19 23: @{[%hash]} -> Tom 19 Sam 0 Joe 17 24: @{[keys %hash]} -> Tom Sam Joe 25: @{[values %hash]} -> 19 0 17 26: @{[map { "$_:$hash{$_}" } keys %hash]} -> Tom:19 Sam:0 Joe:17 31: @{[map { "$_:$hash{$_}" } keys %hash]} -> Tom:20 Sam:1 Joe:18 36: @{[map { "$_:$hash{$_}" } keys %hash]} -> Tom:21 Sam:2 Joe:19 39: @{[map { "$_:$hash{$_}" } keys %hash]} -> Tom:22 Sam:3 Joe:20
- このように、キーに対応する値を返します。
=>
演算子はコンマ演算子と同じ働きをしますが、左オペランドの値を必ず文字列として扱うため、ハッシュを生成するときに多く用いられます。また、コンマを使うよりもキーと値の対応が明確になるという利点もあります。- ハッシュはキーと値が関連付けられたリストです。
- 値の参照
$ハッシュ変数 { キー }
- 値の参照を左辺値にすると、既存のハッシュエントリーの値の更新、あるいは存在しないキーを持ったエントリーを追加できます。
- エントリーの削除
delete $ハッシュ変数 { キー }
- ハッシュの順序
%age = ( Tom => 30, Joe => 20, ); print <<EOS; @{[%age]} EOS
- 実行結果(1)
Tom 30 Joe 20
- 実行結果(2)
Joe 20 Tom 30
- 実行するたびに、実行結果(1)と実行結果(2)がランダムに出力されます。
- 配列はデータの並び順が決まっていますが、キーと値がペアになっているということのみが保証され、データの順番は保証されません(保証されないどころか、セキュリティ強化のため、参照するたびに順序が変わります[1])。
- 特に Perl 5.18.0 以降は、ハッシュ実装に対する「アルゴリズム複雑化攻撃」( Algorithmic Complexity Attacks )に対して十分な強度を得るよう「ハッシュシードのランダム化」「ハッシュトラバーサルのランダム化」「バケット順序の撹乱」「新しいデフォルトのハッシュ関数」「代替ハッシュ関数 Siphash」などのセキュリティ強化が行なわれています[2]。
- 他言語の類似機能
- JsvaScript
- Objectオブジェクト(ルートオブジェクト)がハッシュ(連想配列)です。が、objectはプロトタイプも含むので Mapオブジェクトのほうがより近いです。
- Python
- 辞書型
- Ruby
- Hashクラス
- AWK
- AWKの配列は連想配列です。
Perlが扱うデータ
[編集]Perlでは、演算子によってオペランドの型が決まるので、それに合わせて暗黙の型変換が起こります。 これは、明示的な型変換の手間を省く一方、プログラマーの意図とは異なる変換が行なわれる危うさも含んでいます。
- 例
use v5.12; # v5.12 は use strict の機能を含んでいます。 use warnings; my $x = 52; my $y = "nd street"; say $x + $y; say $x . $y;
- 実行結果
52 52nd street
- 実行時エラー
Argument "nd street" isn't numeric in addition (+) at Main.pl line 6.
use warnings;
で警告を有効にしたので、文字列が0に暗黙変換されたことが指摘されています。
文字列リテラル
[編集]「Hello World」 のような文字列をPerl で扱う場合、"
(ダブルクォーテーション)で囲みます。
- 例
"Hello World"
シングルクォーテーション ' ' で文字列を囲むことも出いますが、
\n
などのバックスラッシュエスケープシーケンスが置換わらない。- 変数や式が展開されない。
この2点がダブルクォーテーションで囲んだ場合と異なります。
型強制
[編集]- 例
use v5.30.0; use warnings; my $x = "123"; my $y = 654; say $x . $y; say $x + $y;
- 実行結果
123654 777
.
(ピリオド ; 文字列連結演算子) は、両辺を文字列に暗黙のうちに変換して連結した文字列を返します。+
(プラス ; 数値加算演算子) は、両辺を数値に暗黙のうちに変換して和を返します。
このように、Perlでは演算子がオペランドを暗黙に変換するので、演算子ごとのオペランド型の理解が大切になります。
引用符と引用符類似演算子
[編集]通常、引用符で囲まれた文字列は、リテラル値として考えられていますが、Perlでは演算子として機能し、様々な種類の補間やパターンマッチの機能を提供します。 Perlでは、これらの動作のために通常の引用符が用意されていますが、任意の引用符を選択する方法も用意されています。 次の表では、{}は区切り文字のペアを表しています。
引用符と引用符類似演算子 慣用表記 汎用表記 意味 変数や式の展開 '' q{} リテラル 不可 "" qq{} リテラル 可 `` qx{} コマンド 可† qw{} 単語リスト 不可 // m{} パターンマッチ 可† qr{} パターン 可† s{}{} 置換 可† tr{}{} 変換 不可‡ y{}{} 変換 不可‡ <<EOF ヒアドキュメント 可† †:'' がデリミタでない場合に限ります。
‡:一定の条件で可 tr の項目参照。
数値
[編集]Perlの数値は、内部的にはネイティブな整数・ネイティブな浮動小数点数・数値を示す文字列で記憶します。 数値リテラルは、10進数、2進数(0bを前置), 8進数(0あるいは0oを前置), 16進数(0xを前置)によって数値を表現できます。 また、指数表現も可能です。
- 例
use v5.34; use warnings; print <<EOS; 42\t--> @{[ 42 ]} 0b1101\t--> @{[ 0b1101 ]} 0177\t--> @{[ 0177 ]} 0o333\t--> @{[ 0o333 ]} 0xff\t--> @{[ 0xff ]} 3.14\t--> @{[ 3.14 ]} 5.00e3\t--> @{[ 5e3 ]} EOS
- 実行結果
42 --> 42 0b1101 --> 13 0177 --> 127 0o333 --> 219 0xff --> 255 3.14 --> 3.14 5.00e3 --> 5000
非数:NaNと無限大:Inf
[編集]Perlは、数値としての非数(NaN)と無限大(Inf)をサポートしています。 ただし、大概のNaNやInfが返りそうな演算では例外が上がって来ますし、数値リテラルとしての NaN や Inf はなく、"NaN" と "Inf" をつかいます。 このとき、大文字小文字を問わず単純な先頭一致なので、以下のような少し面倒な状況がおこります。
- 例
use v5.30.0; # v5.12 以降は use strict の機能を含んでいます。 use warnings; eval { my $x = 1.0/0.0 }; # JavaScript, Ruby では無限大がかえる warn $@ if $@; # Perl では、Illegal division by zero eval { my $x = 0.0/0.0 }; # JavaScript, Ruby では非数がかえる warn $@ if $@; # Perl では、Illegal division by zero say 0+"information"; say 0+"nano"; my $huge = 10**1010; say $huge; say -$huge; say $huge - $huge;
- 実行時の警告
Illegal division by zero at Main.pl line 4. Illegal division by zero at Main.pl line 7. Argument "information" isn't numeric in addition (+) at Main.pl line 10. Argument "nano" isn't numeric in addition (+) at Main.pl line 11.
- 実行結果
Inf NaN Inf -Inf NaN
- 数値としての無限大は ”Inf” に、非数は ”NaN” に正規化されます。
- 単純な文字列から数値への変換(Perlが常々行う暗黙の強制変換でも)無限大や非数に転んでしまう危うさあることを示しています。
変数 $n
があるとき、 $n != $n
が真なら NaN、abs($n) == "Inf"
が真なら Inf または -Inf です。
演算誤差と精度保証
[編集]Perlに限らず、数値計算には誤差が伴います。 例えば、0.01 を 100 回足しても 1 にはなりません。 これを保証する方法はいくつかありますが、ここではカハンの加算アルゴリズムを紹介します。
- 例
use v5.30; # v5.12 以降は use strict の機能を含んでいます。 use warnings; my ( $delta, $iter ) = ( 0.01, 100 ); my $sum = 0.0; $sum += $delta foreach ( 1 .. $iter ); say sprintf "素朴な実装:\t\t%.55f", $sum; $sum = 0; my $c = 0; foreach ( 1 .. $iter ) { my $y = $delta - $c; my $t = $sum + $y; $c = ( $t - $sum ) - $y; $sum = $t; } say sprintf "カハンの加算アルゴリズム:\t%.55f", $sum; use List::Util qw(sum); my @v; push @v, $delta foreach ( 1 .. $iter ); say sprintf "List::Util::sum:\t%.55f", List::Util::sum @v;
- 実行結果
素朴な実装: 1.0000000000000006661338147750939242541790008544921875000 カハンの加算アルゴリズム: 1.0000000000000000000000000000000000000000000000000000000 List::Util::sum: 1.0000000000000006661338147750939242541790008544921875000
- List::Utilモジュールのsumが、素朴な実装と同じ値というのはいがいでした。
特殊変数
[編集]プログラマーが変数を宣言しなくても、いくつかの変数は機能が決まっていて、事前にPerlに用意されており、このような変数を特殊変数あるいは処理系定義済み変数と言います。
プログラム名
[編集]たとえば特殊変数 $0 は、プログラム名が代入されています。
- 例
use v5.12; # v5.12 は use strict の機能を含んでいます。 use warnings; say $0; say `ps -x`; $0 = "(secret)"; say $0; say `ps -x`;
- 実行結果
Main.pl PID TTY STAT TIME COMMAND 10 ? S 0:00 /bin/sh ./exec_command 11 ? S 0:00 perl Main.pl 12 ? R 0:00 ps -x (secret) PID TTY STAT TIME COMMAND 10 ? S 0:00 /bin/sh ./exec_command 11 ? S 0:00 (secret) 13 ? R 0:00 ps -x
- $0 は、値を参照するだけでなく上書きすることもできます。
- 値を上書きすると、Perlのスクリプトから参照できる値が変わるだけでなく、環境も書換えます。
$^O:OS名
[編集]$^T:プロセス開始時刻
[編集]$^V:perlインタープリタバージョン
[編集]$$:プロセスID
[編集]- 例
use v5.12; # v5.12 は use strict の機能を含んでいます。 use warnings; print <<EOS; \$^O: $^O\t-- OS名 \$^T: $^T\t-- プロセスの開始時刻(エポックからの通算秒) \$^V: $^V\t-- perl インタープリターバージョン \$\$: $$\t-- Process ID EOS
- 実行結果
$^O: linux -- OS名 $^T: 1668142037 -- プロセスの開始時刻(エポックからの通算秒) $^V: v5.30.0 -- perl インタープリターバージョン $$: 11 -- Process ID
コンテキスト
[編集]変数、関数、定数などが、式の中でどのように評価されるか決定するものです。
大別するとスカラー・コンテキストとリスト・コンテキストがあり、スカラー・コンテキストにおかれた値はスカラーとして、リスト・コンテキストにおかれた値はリストとして評価されます。
コンテキストと実際のデータが食い違っている場合、次のような規則で評価されます。
- スカラー・コンテキストにリストがおかれた場合、リストの最後の要素が評価されます(コンマ演算子の為)。
- リスト・コンテキストにスカラーがおかれた場合、そのスカラー1個だけを要素とするリストであると解釈されます。
どのようにコンテキストが提供されるか、以下にいくつか例を示します。
代入式は右辺に、左辺と同じコンテキストを提供します:
- コード例
my @array = qw(Foo Bar Baz); my $var = @array; print $var
- 実行結果
3
- qw は、つづく丸カッコ内をスペースで区切ってリスト化する演算子です。
配列はスカラー・コンテキストで評価されるとその要素数を返すので、結果として$numberには3が代入されます。
ただしこのような結果になるのは配列だけです。
前述したとおり、リストがスカラー・コンテキストで評価されると、最後の要素が返されます:
- コード例
my $var = qw(Foo Bar Baz); my ($foo, $bar, $baz) = 'Foo'; print <<EOS; $var ($foo), ($bar), ($baz) EOS
- 実行結果
Baz (Foo), (), ()
- 右辺はスカラーですが、左辺がリスト値を期待している為、1つの要素'Foo'のみを持つリストと解釈されます。
これが $foo に代入されますが、残りの2つの変数については、対応する右辺値がない為未定義となります。
したがって、これは次のコードと等価です:
my ($foo, $bar, $baz) = ('Foo', undef, undef);
フォワード宣言されたサブルーチンは、デフォルトで引数にリスト・コンテキストを提供します:
sub user_func; user_func 'foo', 'bar', 'baz';
これは次のように解釈されます。
user_func('foo', 'bar', 'baz');
つまり、括弧のないサブルーチン呼び出しはリスト演算子として扱われます。
もしこれを、
user_func('foo'), 'bar', 'baz'
と解釈させたいのなら、フォワード宣言にプロトタイプを付加することによって単項演算子として解釈させることができます:
sub user_func($); # 実装にもプロトタイプが必要 user_func 'foo', 'bar', 'baz';
スカラー・コンテキストはさらに
に細分され、評価されます。
文字列コンテキスト
[編集]長さに制限のない文字列として扱われます。
数値はそのまま文字列に変換され、未定義値は空文字列になります。リファレンスも文字列になりますが、文字列として処理されたリファレンスを再びリファレンスに戻すことはできません:
my ($var, $refvar, $refstr); $var = 'foo'; $refvar = \$var; #$$refvar eq 'foo' $refstr = "$refvar"; # 文字列として格納 $$refstr; #エラー; $refstrはもはやリファレンスではない
数値コンテキスト
[編集]数値リテラルとして解釈できる文字列は数値として扱われます。それ以外の文字があるとそこで解釈が終了します。
- 例
#!/usr/bin/perl use v5.12; use warnings; say sprintf __LINE__ . ": %d", 0 + '12345'; say sprintf __LINE__ . ": %d", 0 + '12345abcde'; say sprintf __LINE__ . ": %d", 0 + '123.45e2'; say sprintf __LINE__ . ": %d", 0 + '0b11000000111001'; say sprintf __LINE__ . ": %d", 0 + '012345'; say sprintf __LINE__ . ": %#x", 0 + '0x12345'; say sprintf __LINE__ . ": %#o", oct '12345'; say sprintf __LINE__ . ": %#x", hex '12345';
- 実行時エラー
Argument "12345abcde" isn't numeric in addition (+) at Main.pl line 6. Argument "0b11000000111001" isn't numeric in addition (+) at Main.pl line 8. Argument "0x12345" isn't numeric in addition (+) at Main.pl line 10.
- 実行結果
5: 12345 6: 12345 7: 12345 8: 0 9: 12345 10: 0x12345 11: 012345 12: 0x12345
- 先頭に'0'があっても8進数とは解釈されませんが’0x’が先頭にあると16進数として評価されます。
- 基数を明示して変換するには oct() 関数や hex() 関数を利用します。
真偽値コンテキスト
[編集]ifやwhileなどの制御構文や修飾文、andやorなどの論理演算子が提供するコンテキストです。
偽となるものは:
- 数値
0
- 要素数0の配列
- 要素数0のリスト
- 要素数0のハッシュ
- 文字列
'0'
- 空文字列
''
- 未定義値
undef
であり、残りは全て真と解釈されます。
「文字列'0'」とは'0'という文字列のことであり、数値コンテキストで0と解釈される文字列全てのことではないので注意してください。
次のものは全て真となります:
'0.0'; 'aaa'; '0 but true';
無効コンテキスト
[編集]評価した結果が捨てられてしまうので、値を期待しないコンテキストです。戻り値のない関数呼び出しなど、副作用を目的として使われます。
副作用もないコードは、perlに-wスイッチをつけて実行すると警告が発せられます:
'literal';
型グロブ
[編集]Perlでは異なるデータ型に対して同じ識別子を与えることができます:
$foo = 'bar'; @foo = ( 'bar', 'baz' ); %foo = ( bar => 'baz' ); sub foo { return 'bar' };
Perl処理系は内部に識別子テーブルと呼ばれるハッシュを持っています。そのキーは識別子であり、対応する値は型グロブというデータ構造です。型グロブは同じ識別子を持つすべてのデータ型へのリファレンスを格納しています。つまり上記の例だと識別子'foo'の型グロブにはスカラー、配列、ハッシュ、サブルーチンという4つのデータ型へのリファレンスが格納されています。型グロブは識別子の前に'*'というプレフィックスを付加して表現されます:
*foo;
型グロブ自身はリファレンスを格納したハッシュであり、キーはデータ型の名前です:
*foo{SCALAR}; # \$foo *foo{ARRAY}; # \@foo *foo{HASH]; # \%foo *foo{CODE}; # \&foo *foo{GLOB}; # \*foo; 自分自身へのリファレンス *foo{IO}; # ファイルハンドル *foo{FORMAT} # フォーマット
型グロブへの代入
[編集]型グロブもデータ構造の一つですから、代入や評価ができます。型グロブに別の型グロブを代入すると、変数の別名(エイリアス)を定義することが出来ます:
$foo = 'FOO'; @foo = ( 'FOO', 'BAR' ); *bar = *foo; $bar = 'BAR'; push( @bar, 'BAZ' ); print $foo, "\n"; #BAR print @foo, "\n"; #FOOBARBAZ
これはかつてPerlにリファレンスがなかった頃、サブルーチンに引数を参照渡しするのに利用されていました。また、ファイルハンドルとフォーマットにはプレフィックスが存在しないので、これらを受け渡しする場合の唯一の手段でもありました。
for ( $i = 0; getline( *line ) != -1; $i++ ) { print "line $i: $line"; } sub getline { local (*l) = @_; return defined( $l = <STDIN> ) ? length( $l ) : -1; }
現在ではリファレンスが利用できるので、型グロブを使う必要はありません。ファイルハンドルやフォーマットに関してもIOモジュールなどでオブジェクトとして扱うことができます。
なお、型グロブは識別子テーブルの実体そのものですから、ブロックに結び付けられたレキシカルスコープにすることはできません。言い換えると、local変数にはできるがmy変数にはできません。
また、特定のデータ型のリファレンスを代入すると、そのデータ型に限定して別名を定義できます:
$foo = 'FOO'; @foo = ( 'FOO', 'BAR' ); *bar = \@foo; #配列のみ別名を定義 $bar = 'BAR'; push( @bar, 'BAZ' ); print $foo, "\n"; #FOO print @foo, "\n"; #FOOBARBAZ
*qux = \&Foo::Bar::baz; # Foo::Barモジュールのbaz関数をqux関数としてインポートする
- ^ PHPやRubyではハッシュも順序は保証されます。Perlでは、このような用途に Tie::Hash モジュールを使います
- ^ Algorithmic Complexity Attacks