ハンドルネームの敬称は省略できます

🦀パソコンを叩く日々🐈

令和の時代にPerlに入門する

こんにちは、id:rokuokunです。

Perlとの出会いは突然やってきます。

いつ求められてもサッと対応できるように、いち早くPerlを書けるようになっておきましょう。

perl --version

今回入門するにあたり使用するバージョンは Perl 5.40.0 です。

インストール作業については割愛しますが、困ったらplenvを使っておけばいいと思います。

❯ perl --version

This is perl 5, version 40, subversion 0 (v5.40.0) built for darwin-2level

Copyright 1987-2024, Larry Wall

Perl may be copied only under the terms of either the Artistic License or the
GNU General Public License, which may be found in the Perl 5 source kit.

Complete documentation for Perl, including FAQ lists, should be found on
this system using "man perl" or "perldoc perl".  If you have access to the
Internet, point your browser at https://www.perl.org/, the Perl Home Page.

github.com

目次

はじめに

Perl に入門するにあたって、以下の資料を眺めておくとなんとなく雰囲気が掴めるはずです。

それぞれ内容はほとんど同じですが、スライドで講義的に勉強するならSpeaker Deckをみると良い気がします。(90枚の超大作、すごい。)

scrapbox.io

speakerdeck.com

大事な基礎知識

比較的安全にスクリプトを扱うために

Perlスクリプトを書く前に、日本語や警告をいい感じに扱うためのおまじないをいくつか書きます。

use strict;
use warnings;
use utf8;

以上のコードをPerlスクリプトの先頭に書くことで、厳しい文法チェックと警告文、日本語対応を行うことができます。

いい感じにスクリプト言語してて面白い。

変数

変数の(定義|宣言|初期化)は組込み関数のmyで行います。

my $var1;
my $var2 = "Hello World";
my $var3 = 100;

perlfunc - Perl builtin functions - Perldoc Browser

Perlの組み込み関数 my の翻訳 - perldoc.jp

シジル

変数名の先頭にある$はシジルといい、Perlでのデータ型(構造)を示す記号です。

Perl のデータは、スカラ(Scalar)、スカラの配列(Array)、スカラの連想配列(Hash)の3種類あり、それぞれを示すシジルは$@%です。

# スカラ(Scalar)
my $var_scalar = "Hello";

# 配列(Array)
my @var_array = (1, 2, 3);

# 連想配列(Hash)
my %var_hash_1  = ('key1', 'val1', 'key2', 'val2');
my %var_hash_2 = (
    'key1' => 'value1',
    'key2' => 'value2',
);

perldata - Perl data types - Perldoc Browser

perldata - Perl のデータ型 - perldoc.jp

配列やハッシュの中身からデータを取り出す時は以下のようにします。

# 配列(Array)
my @var_array = (1, 2, 3);

# 連想配列(Hash)
my %var_hash_1  = ('key1', 'val1', 'key2', 'val2');

# データの取り出し
my $value_from_array = $var_array[0];
my $value_from_hash = $var_hash_1{'key1'};

コンテキスト

Perl にはコンテキスト(Context)という概念があります。

意味はそのまま文脈として捉えることができ、評価するときに要求しているデータ型がスカラなら、スカラコンテキスト、リストの場合はリストコンテキストと呼ばれます。

例えば以下の例では、変数への代入式において右辺の配列を評価していますが、評価のされ方がコンテキストによって異なることがわかります。

my @array = (0, 1, 2);

my $context_scalar = @array;
# → 3
my @context_list = @array;
# → (0, 1, 2)

スカラ変数に対する代入では(文脈的に)スカラが求められるため、@arrayはスカラとして評価され、自身の長さを返しています。

対して、配列変数に対する代入では(文脈的に)配列が求められるため、@arrayは配列として自身を評価して、配列を返しています。

https://perldoc.perl.org/perldata#Context

https://perldoc.jp/docs/perl/5.40.0/perldata.pod#Context

リファレンス

リファレンスはArrayやHashを参照を持つ(シンボリックリンク|ポインタ)のようなものです。

これを使うことにより、あらゆるものをスカラとして扱うことができます。(シジルを考えなくてよい!すごい!)

# 普通のスカラ変数
my $var_1 = 100;
# Array変数のリファレンス(糖衣構文)
my $array_ref = [1, 2, 3];
# Hash変数へのリファレンス(糖衣構文)
my $hash_ref = {a => 1, b => 2};

配列やハッシュに対するリファレンスはPerl内部でそれぞれARRAY、HASHと書かれます。

perlref - Perlのリファレンスとネストしたデータ構造 - perldoc.jp

perlcheat - Perl 5 チートシート - perldoc.jp

# Array変数のリファレンス(糖衣構文)
my $array_ref = [1, 2, 3];
# Hash変数へのリファレンス(糖衣構文)
my $hash_ref = {a => 1, b => 2};

# データの取り出し
my $value_from_array_ref = $array_ref->[0];
my $value_from_hash_ref = $hash_ref->{'a'};

基本的にはこのリファレンスを使う方が読みやすく、ミスがないと思います。

関数

基本的な関数は以下のように書くことができます。

sub add {
    my ($a, $b) = @_;
    return $a + $b;
}

my $result = add(2, 3);
# → 5

引数については、@_というその関数に渡された引数が入る暗黙的な変数が存在します。 扱いとしてはC言語などでの**argvなどに似ている気がします。

@_をリストコンテキストで受け取り、分割代入のような形をとることで引数を初期化しています。 (引数を指定できないのはなかなか不便ですね...。)

最後にreturnと書くことで値を返すことができますが、このreturnは省略することが可能です。returnを省略した場合、関数の最後に評価されたものを返します。

また、関数の呼び出しにおいて、括弧を省略することができます。

sub add {
    my ($a, $b) = @_;
    return $a + $b;
}

my $result = add 2, 3;

引数が複数ある場合はやや気持ち悪さを感じますが、引数が一つの場合はさほど違和感がないと思います。

例えば、引数にある変数が定義されているか(undefではない)ことを確かめる関数definedを使用するケースを考えます。

my $var;
my $is_defined = defined $var;

括弧がないことで自然言語に近い形で書くことができて違和感はそこまでないと思います。

この括弧が省略できることは後になってオッ!と感じる時があるでしょう。

perlsub - Perl のサブルーチン (ユーザー定義関数) - perldoc.jp

正規表現

正規表現Perlの目玉と言っていいほど、強力な武器です。

=~正規表現にマッチするかをそのまま検証できます。(すごい)

print "It matches\n" if "Hello World" =~ /World/;
# → It matches

perlrequick - Perl 正規表現のクイックスタート - perldoc.jp

パッケージ

パッケージはPerlの中の名前空間のようなものです。

以下のように書くことでfuncという関数はSampleというパッケージに属します。呼び出す時はSample::func();で呼び出すことができます。

package Sample;

sub func{
    print "Hello, world!\n";
}

Perlの組み込み関数 package の翻訳 - perldoc.jp

perlsub - Perl のサブルーチン (ユーザー定義関数) - perldoc.jp

クラス

Perl には元々オブジェクト指向に基づいた仕組みは存在しません。

しかし、blessとハッシュ、パッケージを利用することでそれに近いものを手作りすることができます。

サンプルとして、./lib/Sample.pmに以下の内容を書きます。

package Sample;

sub new {
    my $class = shift;
    my $self = {
        _name => shift,
        _age => shift,
    };
    bless $self, $class;
    return $self;
}

sub hello {
    my ($self) = @_;
    print "Hello, my name is $self->{_name}\n";
}

1;

呼び出す側(./main.pl)は以下のように書くことで、Sampleをクラスのように扱うことができます。

use lib 'lib';
use Sample;

my $object = Sample->new('Foo', 20);
$object->hello;
# → Hello, my name is Foo

継承

クラスを継承する場合は、use parent '親クラス名';を使います。

package Child::Sample;

use parent 'ParentClass';

sub new {
    my $class = shift;
    my $self = {
        _name => shift,
        _age => shift,
    };
    bless $self, $class;
    return $self;
}

sub hello {
    my ($self) = @_;
    print "Hello, my name is $self->{_name}\n";
}

1;

便利なやつ

Data::Dumper

Data::Dumperは引数にあるリファレンスを見やすい形でdumpしてくれます。

use Data::Dumper;とすることで利用できます。

use Data::Dumper;

# やや複雑なデータ
my $complex_data = {
    name => 'John',
    age => 25,
    address => {
        city => 'New York',
        street => 'Broadway',
    }
};

print Dumper $complex_data;

Dumperを利用することで以下のような形で表示してくれます。

$VAR1 = {
          'name' => 'John',
          'age' => 25,
          'address' => {
                         'city' => 'New York',
                         'street' => 'Broadway'
                       }
        };

DDP

DDPは別名「Data::DumperよりもPrettyに表示するよくん」です。

use DDP;

my $complex_data = {
    name => 'John',
    age => 25,
    address => {
        city => 'New York',
        street => 'Broadway',
    }
};

p $complex_data;

以下のように表示してくれます。(実際は色付きです)

{
    address   {
        city     "New York",
        street   "Broadway"
    },
    age       25,
    name      "John"
}

上からDDP、Data::Dumper、標準print

ぼくのPerlお気に入りポイント

関数呼び出しの括弧が省略できる

definedとかexistsとかscalarとかその辺の関数は括弧がない方がイケてて、逆にそうあるべきな雰囲気がする。

mapなどでワンライナーにできて気持ち良い

forで数行ある処理が、1行で簡潔に書けたらスッキリしてサイコー。

初見のぼくが思ったPerlイケてないポイント

引数を@_で受け取るところ

引数はsub func(a, b) { .. }みたいに書きたかった。

配列の罠

素の配列で二次元配列作れない時に受けた衝撃があり、Hashの値に配列を渡すと展開されてキーとかに伝播しちゃう現象など初見殺しがあるところ。

use DDP;

my %hash = (
    name => 'Yamada',
    favorites => ('apple', 'orange', 'banana'),
);

p %hash;

出力はこうなる。配列が展開されて、予期しない形になる。

{
    favorites    "apple",
    name     "Yamada",
    orange   "banana"
}

ここがすごいぞ令和のPerl

色々と言われてきた(らしい)Perlですが、やはり令和にもなるとナウでヤングな流れを汲んで素晴らしい成長を遂げています。

確かに他の言語からはしばし遅れをとりましたが、今や巻き返しの時です。

サブルーチンシグネチャ(仮引数ありの関数宣言)

関数を作る際、今までは仮引数(シグネチャ)を指定することができませんでした。

perl v5.36.0からは仮引数を定義した状態での関数を作成できるようになりました。

# 従来の方法
sub add {
    my ($a, $b) = @_;
    return $a + $b;
}

my $result = add(2, 3);
# perl v5.36 の機能を有効化
use v5.36;

sub add ($a, $b) {
    return $a + $b;
}

my $result = add(2, 3);
# → 5

互換性維持のため、use v5.36;と書く必要はありますがかなり便利になりました。(use v5.40;でも問題ありません)

perl5360delta - perl v5.36.0 での変更点 - perldoc.jp

perlsub - Perl のサブルーチン (ユーザー定義関数) - perldoc.jp

defer (スコープ終了時実行)

deferはスコープが終了する際に実行するコードを記述することができます。(Go とほとんど同じノリで書ける!!すごい!!)

deferはExperimentalな機能のため、実行前にwarningsを抑える必要があります。

use feature 'defer';
no warnings qw(experimental::defer);

sub sample {
    print "Hello, World!\n";
    defer {
        print "Bye, World!\n";
    };

    print "Hello, Perl!\n";
}

sample();

出力は以下のようになります。

Hello, World!
Hello, Perl!
Bye, World!

例外によって関数が終了したときでも実行されます。

use feature 'defer';
no warnings qw(experimental::defer);

sub sample {
    print "Hello, World!\n";
    defer {
        print "Bye, World!\n";
    };

    die "Error!";
}

sample();

print "End of script\n";

実行結果は以下のようになります。

$ perl main.pl
Hello, World!
Error! at main.pl line ~.
Bye, World!

perlsyn - Perl の文法: 宣言、文、コメント - perldoc.jp

true / false (真偽値)

trueやfalseの真偽値がPerlでも堂々登場です。

use builtin;

my $boolean = false;

print "Hello, World!\n" unless $boolean;
# → Hello, World!

builtin - 組み込みユーティリティ関数をインポートする Perl プラグマ - perldoc.jp

try / catch / finally (例外処理)

例外処理を行うtry / catch構文がPerlでも書けるようになってます。

Perl 5.40.0からtry/catch構文はExperimentalではなくなっているので、そのまま書くことができます。

use v5.40;

try {
    die "ERROR!!";
} catch ($e) {
    print "Failure\n";
}
# → Failure

finallyは未だExperimentalのため、実行にはwarningsを抑える必要があります。

use v5.40;
no warnings qw(experimental::try);

try {
    die "ERROR!!";
} catch ($e) {
    print "Failure\n";
} finally {
    print "Hello from finally\n";
}

perlsyn - Perl の文法: 宣言、文、コメント - perldoc.jp

class

Perl にクラスがやってきたぞ!!

機能を有効化して、warningsを抑えることで使用が可能になります。

use feature 'class';
no warnings qw(experimental::class);

class Human {
    field $name :param(name);
    field $age :param(age);

    method hello {
        print "Hello, my name is $name and I'm $age years old\n";
    }
}

my $alice = Human->new(name => 'Alice', age => 20);
$alice->hello;
# → Hello, my name is Alice and I'm 20 years old

コンストラクタに渡された引数は:paramで受け取ることができます。値のマッピングは変数名と同じものを受け取りますが、:param(別名)のようにすることで別名で受け取ることもできます。

また、:param = デフォルト値とすることで引数のデフォルトの値を定義することができます。

class Human {
    field $age :param(age) = 18;
    field $first_name :param(name);

    method hello {
        print "Hello, my name is $first_name and I'm $age years old\n";
    }
}

my $alice = Human->new(name => 'Alice');
$alice->hello;
# → Hello, my name is Alice and I'm 18 years old

インスタンス作成時に行いたい処理はADJUSTで定義することができます。

use feature 'class';
no warnings qw(experimental::class);

class Human {
    field $age :param(age) = 18;
    field $first_name :param(name);

    ADJUST {
        die "Age must be greater than 0" if $age < 0;
    }

    method hello {
        print "Hello, my name is $first_name and I'm $age years old\n";
    }
}

my $alice = Human->new(name => 'Alice', age => -10);
$alice->hello;
# Age must be greater than 0 at ~

継承についてももちろんできます。

継承は:isaで親クラスを指定することができます。指定できる親クラスは1つまでです。

class Example::Base { ... }

class Example::Subclass :isa(Example::Base) { ... }

perlclass - Perl クラス構文リファレンス - perldoc.jp

おわりに

この記事はPerlに入門した時の記録として残したものです。間違いなどあればこっそり教えてください。こっそり直します。