読者です 読者をやめる 読者になる 読者になる

Perl - リファレンスの説明(3)

Perl 初心者が初心者なりにリファレンスを説明してみるの最終回3回目。1回目 はリファレンスを取得することと、そのリファレンスから元のものにアクセスする(デリファレンスする)方法を書いた。2回目 は無名配列、無名ハッシュをどこかに作ってリファレンスを表す記法 [ ], { } について書いた。今回はリファレンスからデリファレンスをするときの簡潔な記法である矢印記法を説明する。

目次

  1. 復習
  2. 矢印記法の基本
  3. インデックス、キー、引数に挟まれた矢印は省略可能

1.復習

リストをブラケット [ ]、ブレース { } でくくることによって、それぞれ無名配列、無名ハッシュを Perl がどこかに作ってくれて、そのリファレンスだけを表せた。また sub { ... }; によって無名サブルーチンのリファレンスを表せた。

# ソースコード1
my $fruit  = ['apple', 'orange', 'banana'];
my $domain = {jp => 'Japan', kr => 'South Korea', cn => 'China'};
my $hello  = sub { print 'Hello ', shift, "!\n" };

# リファレンスの入った $fruit, $domain, $hello から
# 元の配列、ハッシュ、サブルーチンにアクセスする(デリファレンスする)やり方
print ${ $fruit }[1];   # orange
print ${ $domain }{jp}; # Japan
&{ $hello }('John');    # Hello John!

行列を作る2通りのやり方を紹介した。どちらもよく使う。

# ソースコード2
my @matrix1 = (
    [10, 20, 30],
    [40, 50, 60],
    [70, 80, 90]
);
print ${ $matrix1[0] }[2]; # 30
# ソースコード3
my $matrix2 = [
    [10, 20, 30],
    [40, 50, 60],
    [70, 80, 90]
];
print ${ ${ $matrix2 }[0] }[2]; # 30

無名配列、無名ハッシュを駆使すればネストした複雑なデータも表せる。

# ソースコード4
my $family = {
    family_name => 'Yamada',
    address     => 'Tokyo',
    parents     => [
        {
            name => 'Taro',
            age  => 30
        },
        {
            name => 'Hanako',
            age  => 32
        }
    ],
    children    => [
        {
            name => 'Ichiro',
            age  => 5
        },
        {
            name => 'Jiro',
            age  => 2
        }
    ]
};

# アクセスの例
print ${ $family }{family_name}, "\n";
print ${ ${ ${ $family }{parents} }[0] }{name}, "\n";

for my $child ( @{ ${ $family }{children} } ) {
    print ${ $child }{name}, ' ';
    print ${ $child }{age}, "\n";
}

# 実行結果
# Yamada
# Taro
# Ichiro 5
# Jiro 2

2.矢印記法の基本

ソースコード1のデリファレンスするところを見てみると

${ $fruit }[1]
${ $domain }{jp}
&{ $hello }('John')

のようにしている。実は Perl には次のような決まりがある。

${ ■ }[n]   は ■->[n]   と書ける
${ ■ }{key} は ■->{key} と書ける
&{ ■ }(a)   は ■->(a)   と書ける

この記法(矢印記法と呼ぶ)を使ってみよう。${ $fruit }[1] は ■ のところが $fruit になってると思えば

$fruit->[1] # ${ $fruit }[1] と同じ意味

と書けることがわかる。また、${ $domain }{jp} は ■ のところが $domain と思って

$domain->{jp} # ${ $domain }{jp} と同じ意味

となる。&{ $hello }('John') は

$hello->('John') # &{ $hello }('John') と同じ意味

となる。
ソースコード2、3のデリファレンスのところの ${ $matrix1[0] }[2]、${ ${ $matrix2 }[0] } }[2] にも矢印記法を使ってみる。${ $matrix1[0] }[2] については、■ にあたるのが $matrix[0] だから

$matrix1[0]->[2] # ${ $marix1[0] }[2] と同じ意味

と書ける。次に、${ ${ $matrix2 }[0] } }[2] であるが、これは2段階に分けて考える必要がある。
まず内側の ${ $matrix2 }[0] に着目しよう。■ にあたるのが $matrix2 だと思えば、これは

$matrix2->[0] # 内側の ${ $matrix2 }[0] と同じ意味

と書けることがわかる。これで、

${ ${ $matrix2 }[0] } }[2] は ${ $matrix2->[0] }[2] に等しい

とわかったが、さらに ■ にあたるのが $matrix2->[0] だと思えば

$matrix2->[0]->[2]

に等しいともわかる。まとめると ${ ${ $matrix2 }[0] }[2] は次のように書けるとわかった。

${ ${ $matrix2 }[0] }[2] # 基本形
${ $matrix2->[0] }[2]    # 内側を矢印記法で書く
${ $matrix2 }[0]->[2]    # 説明していないが外側を矢印記法で書けばこうなる
$matrix2->[0]->[2]       # 矢印記法を2回使う

はじめは取っ付きにくいかも知れないが、上記4つの書き方がすべて同じものを表していることをしっかり理解して欲しい。

3.インデックス、キー、引数に挟まれた矢印は省略可能

上で ${ $matrix1[0] }[2]、${ ${ $matrix2 }[0] } }[2] はそれぞれ

$matrix1[0]->[2]
$matrix2->[0]->[2]

と書けることを説明した。ここで今ひとつ矢印記法にはルールがある。それは

インデックス [n] 、キー {key} 、引数 (a) に挟まれた矢印は省略可能

である。$matrix1[0]->[2] の矢印はインデックス [0] と インデックス [2] に挟まれている。よってこのルールによって省略可能で

$matrix1[0][2] # $matrix1[0]->[2] と同じ意味

と書ける。$matrix2->[0]->[2] については2つ目の矢印がインデックスに挟まれている。よって

$matrix2->[0][2] # $matrix2->[0]->[2] と同じ意味

と書ける。(ひとつ目の矢印は省略不可!)

さてソースコード4のデリファレンスのところも矢印記法で書いてみよう。最も見苦しいところ

${ ${ ${ $family }{parents} }[0] }{name}

についてまず考えよう。先程の${ ${ $matrix2 }[0] }[2] のときのように内側から矢印記法に直していけば、

$family->{parents}->[0]->{name}

と書ける。さらに矢印の省略ルール「インデックス、キー、引数に挟まれた矢印は省略可能」によって2つ目、3つ目の矢印は省略可能で

$family->{parents}[0]{name}

とも書ける。随分すっきりした。
残りのところも矢印記法を使って書けば、ソースコード4のデリファレンンスしているところは

# 元の書き方
print ${ $family }{family_name}, "\n";
print ${ ${ ${ $family }{parents} }[0] }{name}, "\n";

for my $child ( @{ ${ $family }{children} } ) {
    print ${ $child }{name}, ' ';
    print ${ $child }{age}, "\n";
}

# 矢印記法を使った書き方
print $family->{family_name}, "\n";
print $family->{parents}[0]{name}, "\n";

for my $child ( @{ $family->{children} } ) {
    print $child->{name}, ' ';
    print $child->{age}, "\n";
}

となる。すっきりした!


3回に渡って Perl のリファレンスについて説明してみた。リファレンスを知れば結構自由度が上がると思う。