このページは、Perl/ 以下のサブページを全て一ページに展開した統合版です。

  • 動的に展開しているので、最新のサブページと内容は同じです。
  • ページ遷移の苦手なスマートフォンやタブレットでの利用に適しています。
  • プリントアウトする場合も一度に全ての内容を出力できます。
  • 「ページ内検索」で Perl/ 以下のサブページの全文検索ができます。

本書は、Perlのチュートリアルです。 Perlは、Practical Extraction and Report Languageの略で、実用的な抽出・報告言語です。 1987年にUNIXの世界に出現したこの言語は、ラリー・ウォールという一人の人間の手によるもので、今日に至るまでPerlの文法改良をリードし続けています。

Wikipedia
Wikipedia
ウィキペディアPerlの記事があります。

Perlの重要な点は、スクリプト言語であるため、プログラムのソースコードを無償で提供することです。このため、例題を通してPerlを簡単に学ぶことができ、また、何千ものスクリプトをダウンロードして、自分用に修正することも可能です。

Perlは、非常にわかりやすく、広く知られているプログラム記述言語です。DOSのバッチファイルやシェルスクリプトに相当するものを作成するなど、さまざまな作業に使われますが、Web開発の文脈では、CGIスクリプトを開発するために使用されます。

しかし、Perlのフリーコードの多くは理解不可能で、全く謎めいたスタイルをしていることが、Perlの悪いところです。

Perlは、非常に強力な言語ですが、その反面、習得が難しい言語でもあります。しかし、Perlを習得すれば、さまざまなことができるようになります。


はじめに

編集

TMTOWTDI

編集

Perl(パール)とは、1987年ラリー・ウォールが開発したプログラミング言語です。PerlはC言語をはじめ、AWKsedのような様々なUNIXソフトウェアの伝統を受け継ぎ、多くの優れた機能を取り入れています。

PerlのモットーはThere's More Than One Way To Do It.(やり方は何通りもある)で、略してTMTOWTDI(ティムトゥディ)などと呼ばれます。これは「正しいやり方がいくつ存在してもよい」という考え方で、Perlの柔軟性を表しています。このことから、Perlは特にテキスト処理やCGIプログラミングの分野で多く用いられています。

PerlはC言語と比べて簡潔であり、移植性が高い言語です。C言語では数百行のプログラムが必要な場合でも、Perlでは数行で書くことができることもあります。

一方で、Perlの文法は省略を多用したり、特殊変数を記号で表すことがありますので、Perlのやり方に慣れるまでには少し時間がかかるかもしれません。しかし、一行でも多くのコードを書いてPerlを身につけることをお勧めします。

前提条件

編集

テキストエディタを用いて例に挙げられる通りのファイルを作成できることを前提としています。プログラミングそのものの概念についてはプログラミング(Wikibooks)プログラミング(Wikipedia)を参照してください。以降、断りのない場合はUnix(およびLinuxなどのUnix互換OS)上で Perl 5.30.0(2019年05月22日 リリース)以降のバージョンを利用しているものとして説明します(かつて Perl6 と呼ばれていた Raku は含みません)。

実行環境

編集

Microsoft Windows

編集

Perl公式サイトではWindowsに対応したパッケージは提供していません。 このため、サードパーティーが提供するPerlのディストリビューションを利用するか、CygwinMSYS2Windows Subsystem for Linuxを利用する方法もあります。

Strawberry Perl

編集
 
Wikipedia
ウィキペディアen:Strawberry Perlの記事があります。

Strawberry Perlは、Microsoft Windowsプラットフォーム用のプログラミング言語Perlのディストリビューションです。 Strawberry Perlには、Mingw-w64 C/C++コンパイラなどのツールチェインが含まれています。 Strawberry Perlは、環境変数はインストール時に設定済みなので、インストール直後に、コマンド プロンプト・Power shellあるいは、Windows Terminalから使うことができます。

2024年6月現在、Strawberry PerlのPerlコアはv5.38.2.2(2023/12/11リリース)です。
Strawberry Perl for Windows

macOS, FreeBSD, その他 UNIX 環境

編集

maxOS, FreeBSD, その他 UNIX 環境ではほとんどの場合、PerlはOSのユーザーランドとは別にパッケージとして提供されています。「ターミナル」を起動し、コマンドプロンプト($, %, >などの記号)に続けてperl -vと入力し、⏎([Enter]または[Return])キーを押し、以下のように表示されればperlが利用可能です。

% perl -v

This is perl 5, version 40, subversion 0 (v5.40.0) built for amd64-freebsd-thread-multi

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.

% _

PerlがインストールされていないUNIX環境

編集

主要なUNIXならは、Perlをサポートしていると思いますが、もしOSの公式パッケージにperlが無い場合でも、 Perl Download - www.perl.orgからバイナリパッケージをダウンロードできます。

バイナリパッケージが存在しないプラットフォームの場合はPerl Download - www.perl.orgからソースコードをダウンロードし、ビルドして実行してください。

GNU/Linux

編集

GNU/Linux の場合、Linux Standard Base(LSB; ISO/IEC 23360:2021)では、準拠するすべてのディストリビューションにPerlをインストールして出荷することが義務付けられています[1]。 しかし、何らかの理由でインストールされていないディストリビューションでは、apt などのパッケージマネージャーを利用してインストールする必要があります。

詳細は、利用しているディストリビューションのパッケージマネージャーの利用法を確認してください。 また、既に Perl がインストールされている環境でも、最新のバージョンを維持するためにパッケージマネージャーを利用して update するよう心がけ、脆弱性があるまま使わないようにしましょう。

作成、実行の流れ

編集

プログラムの作成

編集

Perlはインタプリター言語です。つまり、プログラムを実行するたびに、コンパイル[2]と実行を行うPerlインタプリターが常に必要なのです。 C/C++やJavaのようにプログラムをコンパイルしてから実行するのではなく、プログラムのソースコードを(Perlインタプリターがある)別のコンピューターにコピーして実行するだけで良いのです。

ソースコードの編集には好きなテキストエディタを使うことができます。 OS標準のメモ帳(Windows)、TextEdit(macOS)、vi(UnixやUnix互換OS)などでも十分です。 Wordなどのワードプロセッサーでも編集は可能ですが、標準ではプレーンテキストとして保存されないので注意してください。

Perlで書かれたコードを実行したい場合には、テキストエディタで実行したいコードを書いてセーブ保存してから、コマンド端末から保存したファイルを perl で実行することになります。

デバッガー

編集

Perlにも、Pythonの対話モードのような REPL( Read-Eval-Print Loop ) があります。

デバッガーの起動例
% perl -de 1

Loading DB routines from perl5db.pl version 1.77
Editor support available.

Enter h or 'h h' for help, or 'man perldebug' for more help.

main::(-e:1):   1
  DB<1> say 2**9
512
  DB<2> say "Hello world!"
Hello world!

  DB<3> q
% _
短いコードの確認には、このデバッグモードが便利です。

プログラムの実行

編集

テキストエディタでhello.plというソースファイルを下記のように作成し、

hello.pl
#!/usr/bin/env perl
use v5.30.0;

say "Hello, World";
say "Hello, Perl";
コマンドラインでの実行
% cat > hello.pl
#!/usr/bin/env perl
use v5.30.0;

say "Hello, World";
say "Hello, Perl";
^D
% perl hello.pl 
Hello, World
Hello, Perl
% chmod +x hello.pl 
% ./hello.pl 
Hello, World
Hello, Perl
% _
say は、文字列または文字列のリストを表示(して改行)する組込み関数です。
Perlの文字列リテラルは、"(ダブルクォーテーションマーク)で囲みます。
(シングルクオーテーション)で囲んで文字列リテラルを表現できますが、この場合は改行文字(\n)などのバックスラッシュエスケープや変数($x, @y%z,)が展開されずそのまま表示されるなどの違いがあります。

Perlは、多くのプログラミング言語と同様に、Perlは構造化プログラミングというプログラミングパラダイムを採用しています。 プログラムの流れは逐次・分岐・反復の3つが基本です[3]。 つまり、原則としてプログラムはソースコードに記述された順に実行され、iffor などの制御構造があったときだけ分岐・反復します。

Perlの基本機能の紹介

編集

変数の宣言と初期化

編集

Perlでは、変数は「データを格納する領域(オブジェクト)に付けられた名前」です(動的型付け)。 Perlでは変数名(例ではx)の前に$, %, @などの接頭辞(sigil; シジル)がつく表現を変数として扱います。

コード例(エラーとなります)
#!/usr/bin/env perl
use v5.12;
#use strict; v5.12以降は use strict を含んでいるので以後は略します
use warnings;

$x = "Perl";
say "Hello, $x";
実行結果
Global symbol "$x" requires explicit package name (did you forget to declare "my $x"?) at Main.pl line 6.
Global symbol "$x" requires explicit package name (did you forget to declare "my $x"?) at Main.pl line 7.
Execution of Main.pl aborted due to compilation errors.
  1. シバン(shebang!)で、スクリプトを実行するインタープリターの位置を教えます。
    #!/usr/bin/perlとする例を見かけますが、perl が /usr/bin/perl でなく他の場所(例えば /usr/local/bin/perl)にインストールされている可能性があるので#!/usr/bin/env perlとしました。これは、PATHが通ったディレクトリに perl と言う名前のトロージャンホースを仕掛けられるリスクがあるとの批判がありますが、$ perl hello.plするときにも同じリスクがあり、リスクゼロではありませんが受入れがたいものではないと考えられます。
  2. Perl v5.12 以降の機能を有効にしています。
  3. 安全ではない構文を制限する Perl プラグマ strict は Perl5.12 からディフォルトで有効です[4]
    strict が有効なので、グローバル変数の使用はエラーとなります。
    無効にするのは、 no strict; とします。
  4. 選択的な警告を調整する Perl warnings です[5]
    これは、Perl5.36.0 以降でディフォルトで有効なので 5.36 より前のスクリプトでは、明示的に use warnings; が必要です。

この対策として、キーワード my を使ってレキシカルスコープの変数(レキシカル変数)として宣言します。

コード例(修正後)
#!/usr/bin/env perl
use v5.12;
# use strict; 
use warnings;

my $x = "Perl";
say "Hello, $x";
実行結果
Hello, Perl
6行目を修正した結果、正しく実行できました。
この様にに、use v5.12;以降を指定することで、 安全でないコードを早い段階で発見できます。

Perlでは例にあるような構造のない、ただ一つの値のみを保有するデータ(スカラーと呼びます)のほか、順番に並んだ複数のデータからなる配列、キーと値のペアー複数記録するハッシュ、より複雑なデータ構造を実現するリファレンスなどのデータを扱うことができます。 変数についての詳細は変数とデーター型で解説します。 リファレンスについての詳細はリファレンスで解説します。

式と演算子

編集

Perlには、豊富な数値計算のための演算子が用意されており、それらを組み合わせて式を作ることが出来ます。

コード例
#!/usr/bin/env perl
use v5.12;
use warnings;

say 12 + 5;
say 12 - 5;
say 12 * 5;
say 12 / 5;
say 12 % 5;
say 12 ** 5;
say 12 & 5;
say 12 | 5;
say 12 ^ 5;
say 12 << 1;
say 12 >> 1;
say -12;
say +12;
実行結果
17
7
60
2.4
2
248832
4
13
9
24
6
-12
12
四則 徐 剰余 累乗 ビット間論理積 ビット間論理和 ビット間排他的論理和 右シフト 左シフト 単項マイナス 単項プラス です
use v5.12;とuse warnings;
プラグマ use v5.12;とuse warnings;は必ずプログラム冒頭で指定しましょう。

use v5.12;のv5.12は、自分の使っているPERLインタープリターのバージョン(特殊変数 $^V で確認できます)で良いでしょう。

もし、指定しないと組込み関数のスペルミス程度でも、エラーも警告もなく思いもかけない結果になります。

修正例
$x = cyop;
print $x
実行結果
cyop
コード例
use v5.12;
use warnings;

$x = cyop;
print $x
実行時エラー
Global symbol "$x" requires explicit package name (did you forget to declare "my $x"?) at Main.pl line 4.
Global symbol "$x" requires explicit package name (did you forget to declare "my $x"?) at Main.pl line 5.
Bareword "cyop" not allowed while "strict subs" in use at Main.pl line 4. 
Execution of Main.pl aborted due to compilation errors.

制御構造

編集

原則としてPerlのプログラムは上に書かれているものから順に実行されていきますが、繰り返しや、特定の条件に応じて動作を切り替えることができます。

条件分岐

編集

ある条件をみたしているか(ここでは$x が 3 より大きいかどうか)を判定し、判定の内容により動作を切り替えています。

コード例
#!/usr/bin/env perl
use v5.12;
use warnings;

my $x = 5;
if ($x > 3){
  say "$x > 3"
} else {
  say "$x <= 3"
}
実行結果
5 > 3

反復

編集

反復は、あるブロックを繰り返す制御構造でループとも呼ばれます。

コード例
#!/usr/bin/env perl
use v5.12;
use warnings;

foreach my $num (1 .. 10) {
  print "$num ";
}
say ""
実行結果
1 2 3 4 5 6 7 8 9 10

コメント

編集

プログラミングの作成中に「プログラム内にメモを残しておきたい」「一時的にある動作を実行されないようにしたい」といった要求が生じることがあります。このような「プログラミングのコードに記載されるが動作しない箇所」のことを一般にコメントと呼びます。

コード例
#!/usr/bin/env perl
use v5.12;
use warnings;

# シャープ以降から行末まで実行されない
say "ここはコメントの外";
実行結果
ここはコメントの外
Perlでは#以降の行端までがコメントとみなされます。C言語にあるような複数行にわたるコメントはありませんが、以下のような方法で実質的な複数行コメントを利用することができます。

Podによるドキュメンテーション

編集

PerlのPodは、Plain Old Documentationの略で、Perlスクリプトのドキュメンテーションを書くためのフォーマットです。 Podは、テキストファイルに記述され、スクリプトと一緒に配布されることが一般的です。 Podは、人間が読みやすいドキュメントを作成するために設計されていますが、コンピュータプログラムにも解析可能な単純な構文があります。

以下はPerlで1から100の間の素数をすべて表示するプログラムにPodによるドキュメントも付加した例です。

#!/usr/bin/env perl
use v5.12;
use warnings;

=head1 NAME

find_primes.pl - Find all prime numbers between 1 and 100

=head1 SYNOPSIS

  perl find_primes.pl

=head1 DESCRIPTION

This program finds all prime numbers between 1 and 100 and prints them to the console.

=head1 AUTHOR

Your Name

=cut

# Initialize an array to hold the prime numbers
my @primes = ();

# Loop through all numbers between 1 and 100
for my $num (1..100) {
    my $is_prime = 1;
    
    # Check if the number is divisible by any number other than itself and 1
    for my $divisor (2..int($num/2)) {
        if ($num % $divisor == 0) {
            $is_prime = 0;
            last;
        }
    }
    
    # If the number is prime, add it to the array
    push @primes, $num if $is_prime;
}

# Print the prime numbers to the console
print "Prime numbers between 1 and 100:\n";
print join(", ", @primes) . "\n";
HTMLのレンダリング例
<?xml version="1.0" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <title>find_primes.pl - Find all prime numbers between 1 and 100</title>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <link rev="made" href="mailto:mat@FreeBSD.org" />
  </head>

  <body>
    <ul id="index">
      <li><a href="#NAME">NAME</a></li>
      <li><a href="#SYNOPSIS">SYNOPSIS</a></li>
      <li><a href="#DESCRIPTION">DESCRIPTION</a></li>
      <li><a href="#AUTHOR">AUTHOR</a></li>
    </ul>

    <h1 id="NAME">NAME</h1>

    <p>find_primes.pl - Find all prime numbers between 1 and 100</p>

    <h1 id="SYNOPSIS">SYNOPSIS</h1>

    <pre><code>perl find_primes.pl</code></pre>

    <h1 id="DESCRIPTION">DESCRIPTION</h1>

    <p>
      This program finds all prime numbers between 1 and 100 and prints them to
      the console.
    </p>

    <h1 id="AUTHOR">AUTHOR</h1>

    <p>Your Name</p>
  </body>
</html>

上記の例では、=head1はセクションの見出しを表します。 NAMESYNOPSISは、モジュールの名前と使用例を説明します。 DESCRIPTIONは、モジュールの機能の詳細な説明であり、AUTHORは、モジュールの作者の情報を提供します。

脚註

編集
  1. ^ /usr/bin/perl にPerlインタープリターも実行形式または実行形式へのリンクが有り ⇒ Linux Standard Base Languages Specification 5.0::7.2. Perl Interpreter Location、v5.8.8 以降であることが義務付けられています ⇒Linux Standard Base Languages Specification 5.0::7.3. Perl Interpreter Version
  2. ^ Perlでコンパイルといった場合、Perlインタープリターがソースコードを読込み内部表現に置換えることです。これは移植性・相互運用性の視点からは好ましい特徴で、C/C++やJavaのように実行形式や中間表現をファイルに書出すことではありません。
  3. ^ gotoはあります。が、大域脱出などはラベルと併用したループ制御文で実現できるなど、gotoが必要になるケースは極めて少ないです
  4. ^ strict - Perl pragma to restrict unsafe constructs - Perldoc Browser”. 2022年11月9日閲覧。
  5. ^ warnings - Perl pragma to control optional warnings - Perldoc Browser”. 2021年12月11日閲覧。

</noinclude>


制御構造

編集

Perlの制御構造は、プログラムの実行フローを制御するために使用される構文です。基本的な制御構造には、条件分岐やループが含まれ、Perlの柔軟な構文により、多様な方法で記述することができます。以下に、Perlで使用される主な制御構造について説明します。

条件分岐

編集

if, elsif, else

編集

Perlでは、if文を使用して条件に基づいてコードを実行できます。elsifelseは、追加の条件やそれ以外のケースに対応します。

my $x = 10;

if ($x > 10) {
    say "x is greater than 10";
} elsif ($x == 10) {
    say "x is equal to 10";
} else {
    say "x is less than 10";
}

unless

編集

unlessは、条件が偽のときにブロックを実行するための構文です。ifの逆です。

my $y = 5;

unless ($y > 10) {
    say "y is not greater than 10";
}

後置if/unless

編集

Perlでは、簡潔な記述のために、条件文を後置することもできます。

say "x is equal to 10" if $x == 10;
say "y is not greater than 10" unless $y > 10;

ループ構造

編集

whileループ

編集

条件が真である限り、繰り返し処理を実行します。

my $i = 0;

while ($i < 5) {
    say "i is $i";
    $i++;
}

untilループ

編集

条件が偽の間、ループを繰り返します。whileの逆です。

my $j = 0;

until ($j == 5) {
    say "j is $j";
    $j++;
}

forループ

編集

特定の範囲を繰り返し処理する一般的なループです。

for (my $k = 0; $k < 5; $k++) {
    say "k is $k";
}

foreachループ

編集

リストの要素を順に処理するためのループです。foreachforは同じ動作をします。

my @array = (1, 2, 3, 4, 5);

foreach my $value (@array) {
    say "Value is $value";
}

後置while/until

編集

ifunlessと同様に、ループ条件を後置して記述できます。

my $n = 0;
say "n is $n" while $n++ < 5;

ループ制御

編集

nextは、ループの現在の反復をスキップし、次の反復に進みます。

for my $i (1..5) {
    next if $i == 3;
    say "i is $i";
}

lastは、現在のループを終了させます。breakに相当します。

for my $i (1..5) {
    last if $i == 4;
    say "i is $i";
}

redoは、現在の反復を再実行します。

my $count = 0;

while ($count < 5) {
    say "count is $count";
    $count++;
    redo if $count == 3;
}

条件演算子(3項演算子)

編集

Perlでは、条件演算子を使って簡潔な条件分岐を記述できます。

my $a = 10;
my $b = ($a > 5) ? "Greater than 5" : "5 or less";
say "$b";  # "Greater than 5"

まとめ

編集

Perlの制御構造は非常に柔軟で、多くの場面に対応できます。基本的なifwhileに加え、unlessなど、Perlならではの簡潔な記述が可能です。



変数とデータ型

編集

変数

編集

Perlでは、変数は「データを格納する領域(オブジェクト)に付けられた名前」です。 同じオブジェクトを2つ以上の変数が指し示すこともありますし、配列の要素のように、単体では名前がないオブジェクトもあります。 変数を用いることにより、データをより柔軟に扱うことができます。

変数の利用の形態は大きく分けて宣言代入参照の3種です。

データ型

編集

Perlには3つの組み込みデータ型があります。

スカラー
文字列
サイズは問わず、使用可能なメモリ量に制限されます
数値
整数
処理系固有のネイティブな整数
浮動小数点数
処理系固有のネイティブな整数
文字列で表現された数値
上記の整数・浮動小数点数をPerlの数値リテラルで表現したもの(”NaN” ”Inf” なども含まれます)
参照
これについては リファレンス で説明します
配列
カラーを0から始まる番号でインデックス付けした順序付きコレクション
ハッシュ
スカラー値を関連する文字列キーでインデックス付けした順序付きでないコレクション

接頭辞

編集

変数のデータ型は、接頭辞( 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 のような特殊な値は、囲む必要があります。

変数名を含む識別子の規約

編集
  1. 識別子の先頭は、英大文字・英小文字あるいは ’_’
  2. 識別子の2文字目以降は、英大文字・英小文字・'0'..'9' あるいは ’_’
  3. 識別子の長さは、1文字以上251文字以下
    識別子の正規表現
    [A-Za-z_][A-Za-z_0-9]{0,250}
    
不正な識別子の例
123age
new-word
フラッグ
  1. 先頭に数字は使えない
  2. ’-’は変数名に使えない
  3. 論外

演算子主導の型強制

編集

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文字の文字列のインクリメントでは、桁上りをしています!

このような文字列をオペランドに取ったときのインクリメントをマジカルインクリメントと呼びます。

ほかのプログラミング言語との違い
C/C++と違いPerlでは変数宣言でに、int や double などの型を指定することはありません。

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}が変更部分です。左右の項を入れ替えました。これがコードブロックを渡さなかったときのディフォルト動作です。
2001年9月9日問題
Perlは、Unix時間(1970年1月1日0時からの経過秒数)を扱う際に、この問題に関連した潜在的な不具合がありました。

具体的には、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"

シングルクォーテーション ' ' で文字列を囲むことも出いますが、

  1. \nなどのバックスラッシュエスケープシーケンスが置換わらない。
  2. 変数や式が展開されない。

この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関数としてインポートする

演算子

編集

演算子とは、1つ以上のオペランドを伴って式を構成する構文要素です。 オペランドの数によって、単項演算子・二項演算子・三項演算子に分類されます。 同じ記号を使っても、単項演算子だったり二項演算子であったりする演算子もあります。 問えば、符号反転-$xと減算$x - $y は、同じ記号 - を使います。 さらに、デクリメント--$x も、同じ記号 - を使います(--で1つのトークンで間に空白などは入れられません)。

また。Perlの演算子は、オペランドの型を演算子の想定する型に強制的に型変換され演算が行われます。

$x + $y # 加算。オペランドが数値でない場合は数値に変換してから加算。
$x . $y # 結合。オペランドが文字列でない場合は文字列に変換してから結合。
$x x $y # 繰返し。左オペランドの文字列とみなし、右オペランドを数値とみなし、その回数だけ繰り返す。
このように演算子がわかれば、オペランドの型もわかります。
フランス語の名詞を憶えるときに性も同時に憶えるように、Perl の演算子を憶えるときにはオペランドの型も同時に憶えましょう。
多くのオペランドはスカラーです。
インクリメント++のように数値も文字列もとり得る例外や、二項演算子の x の様にリストを取る例外もありますが、本則を覚えたあと、各個の例外を理解するのが全体を理解する早道です。

演算子の優先度と結合性

編集

演算子の優先順位と結合性は、Perlでは概ね数学の世界と同じように機能します。

演算子の優先順位は、ある演算子が他の演算子よりも強くグループ化されることを意味します。たとえば、2 + 4 * 5 の場合、乗算の方が優先順位が高いので、2 + 4 が乗算の左側のオペランドとしてグループ化されるよりも、4 * 5 が加算の右側のオペランドとしてグループ化されます。つまり、式は (2 + 4) * 5 ではなく、2 + (4 * 5) と書かれるようなもので、6 * 5 == 30 ではなく、2 + 20 == 22 となります。

演算子の優先度と結合方向[3]
演算子 結合方向
項 リスト演算子(左から)
->
++ -- 無結合
**
! ~ ~. \ +項 -項
=~ !~
* / % x
+ - .
<< >>
名前付き単項演算子 無結合
isa 無結合
< > <= >= lt gt le ge 連鎖[4]
== != eq ne <=> cmp ~~ 連鎖/無結合[4]
& &.
| |. ^ ^.
&&
|| //
.. ... 無結合
?:
= += -= *= などの代入演算子 goto last next redo dump
, =>
リスト演算子 (右から) 無結合
not
and
or xor

代入演算子

編集
左辺の変数に右辺のオブジェクトを束縛します。
my, localstate による変数宣言に伴う = は代入ではなく初期化です。
コード例
#!/usr/bin/perl
use v5.30.0;
use warnings;

my $x = 1;   # $x を 1 で初期化
say "\$x = $x";

$x = "abc";  # $x に "abc" を代入
say "\$x = $x";

my @x = ("xyz", 1, 3.14);  # @x を ("xyz", 1, 3.14) で初期化
say "\@x = @x";
say "\$x = $x";

my $y = 0;
$x = $y = 123;
say "\$x = $x, \$y  = $y";
実行結果
$x = 1
$x = abc
@x = xyz 1 3.14
$x = abc 
$x = 123, $y  = 123
$x の値に注目すると、代入するたびに方が違うオブジェクトに束縛されています。
これは正常な動作で、strict プラグマがあってもwarningsプラグマがあっても、違う型の値の代入もエラーも警告も出ません。
@x に代入した後も、$x の値は変わらないので「名前が同じでも接頭辞($ @ や %)が違う変数は別の変数」であることがわかります。
代入は式で、値は代入された値です。
代入は右結合なので、$x = $y = 123$x = ( $y = 123 ) と解されます。

LEFT OP= 右 の形式の演算子は、LEFT = LEFT OP 右 と等価です。 OP= で1つのトークンです。OP= の間に空白や改行があってはいけません。

$x += $y;     # $x = $x + $y と等価
$x -= $y;     # $x = $x - $y と等価
$x *= $y;     # $x = $x * $y と等価
$x /= $y;     # $x = $x / $y と等価
$x %= $y;     # $x = $x % $y と等価
$x **= $y;    # $x = $x ** $y と等価
use v5.30.0;

my $x;
$x .= "abc";
say $x;

$x .= "XYZ";
say $x;
実行結果
abc 
abcXYZ
文字列を結合して代入します。
変数が未定義、あるいはみ初期化の場合(undefの場合)、undefが "" に自動変換され右辺の値と結合…つまり普通の代入が行なわれます。
use v5.30.0;

my $x  = "abc";
$x x= 4;
say $x;
実行結果
abcabcabcabc
繰返して代入します。

算術演算子

編集
四則演算と剰余および累乗です。
四則演算と剰余および累乗
#!/usr/bin/perl

use strict;
use warnings;

foreach my $x(-7, 0, -7) {
    foreach my $y(-3, 1, 3) {
        foreach my $op(qw(+ - * / % **)) {
            my $expr = "$x $op $y";
            print "$expr -> @{[eval $expr]}:\t"
        }
        print "\n"
    }
}
実行結果
-7 + -3 -> -10:	-7 - -3 -> -4:	-7 * -3 -> 21:	-7 / -3 -> 2.33333333333333:	-7 % -3 -> -1:	-7 ** -3 -> -0.00291545189504373:	
-7 + 1 -> -6:	-7 - 1 -> -8:	-7 * 1 -> -7:	-7 / 1 -> -7:	-7 % 1 -> 0:	-7 ** 1 -> -7:	
-7 + 3 -> -4:	-7 - 3 -> -10:	-7 * 3 -> -21:	-7 / 3 -> -2.33333333333333:	-7 % 3 -> 2:	-7 ** 3 -> -343:	
0 + -3 -> -3:	0 - -3 -> 3:	0 * -3 -> 0:	0 / -3 -> 0:	0 % -3 -> 0:	0 ** -3 -> Inf:	
0 + 1 -> 1:	0 - 1 -> -1:	0 * 1 -> 0:	0 / 1 -> 0:	0 % 1 -> 0:	0 ** 1 -> 0:	
0 + 3 -> 3:	0 - 3 -> -3:	0 * 3 -> 0:	0 / 3 -> 0:	0 % 3 -> 0:	0 ** 3 -> 0:	
-7 + -3 -> -10:	-7 - -3 -> -4:	-7 * -3 -> 21:	-7 / -3 -> 2.33333333333333:	-7 % -3 -> -1:	-7 ** -3 -> -0.00291545189504373:	
-7 + 1 -> -6:	-7 - 1 -> -8:	-7 * 1 -> -7:	-7 / 1 -> -7:	-7 % 1 -> 0:	-7 ** 1 -> -7:	 
-7 + 3 -> -4:	-7 - 3 -> -10:	-7 * 3 -> -21:	-7 / 3 -> -2.33333333333333:	-7 % 3 -> 2:	-7 ** 3 -> -343:
除算は浮動小数点数を返すのに、剰余演算は整数を返すことです。
また、剰余演算は -7 % -3 -> -1 と若干癖があります。

インクリメントとデクリメント

編集
インクリメントは変数の値を1増します。
$x++は、$x += 1および$x = $x + 1と等価な演算を行います。
++ は前置すること(++$x)も後置すること($x++)もできます。
インクリメント
#!/usr/bin/perl

use strict;
use warnings;

my $x = 10;
print "\$x = $x\n";

$x++;
print "\$x = $x\n";

++$x;
print "\$x = $x\n";

print $x++ . "\n";
print "\$x = $x\n";

print ++$x . "\n";
print "\$x = $x\n";

my $q;
$q++;
print "\$q = $q\n";
実行結果
$x = 10
$x = 11
$x = 12
12
$x = 13
14
$x = 14 
$q = 1
前置++$xでも後置$x++でも実行結果は同じです。
式の値は
前置++$x
インクリメント後の値
後置$x++
インクリメント前の値
と異なります。
最後の $q はややトリッキーです。
宣言だけで初期化を行なわないスカラー変数の値は undef です。この変数をインクリメントする場合
undef が数値に変換される undef ⇒ 0、変換された 0 をインクリメント ⇒ 1 という反応経路になります。

 

マジカルインクリメント

編集

Perlでは、演算子が決まるとオペランドの型が確定するのですが、インクリメントは例外で、数値のときは $x++ ⇒ $x += 1 ⇒ $x = $x + 1 ですが、文字列を渡すと一風変わった挙動をします。

print <<EOS;
@{[ ++($foo = "99") ]}
@{[ ++($foo = "a0") ]}
@{[ ++($foo = "Az") ]}
@{[ ++($foo = "zz") ]}
EOS
実行結果
100
a1
Ba 
aaa
この文字烈に対する不思議なインクリメントをマジカルインクリメントと呼びます。
デクリメントに、マジカルデクリメントはありません。
デクリメントは変数の値を1減らします。
$x--は、$x -= 1および$x = $x - 1と等価な演算を行います。
-- は前置すること(--$x)も後置すること($x--)もできます。
デクリメント
#!/usr/bin/perl

use strict;
use warnings;

my $x = 10;
print "\$x = $x\n";

$x--;
print "\$x = $x\n";

--$x;
print "\$x = $x\n";

print $x-- . "\n";
print "\$x = $x\n";

print --$x . "\n";
print "\$x = $x\n";

my $q;
$q--;
print "\$q = $q\n";
実行結果
$x = 10
$x = 9
$x = 8
8
$x = 7
6
$x = 6 
$q = -1
前置--$xでも後置$x--でも実行結果は同じです。
式の値は
前置--$x
デクリメント後の値
後置$x--
デクリメント前の値
と異なります。
最後の $q はややトリッキーです。
宣言だけで初期化を行なわないスカラー変数の値は undef です。この変数をデクリメントする場合
undef が数値に変換される undef ⇒ 0、変換された 0 をデクリメント ⇒ -1 という反応経路になります。

文字列連結演算子

編集

.(ピリオド)は、文字列同士を連結して別の文字列を返す演算子、文字列連結演算子です。

#!/usr/bin/perl
use v5.30.0;
use warnings;

say "ABC" . "XYZ";
say "ABC" . "XYZ" x 3;
say "ABC" x 3 . "XYZ";
実行結果
ABCXYZ
ABCXYZXYZXYZ 
ABCABCABCXYZ
. は、二項演算子です。
次節で説明するx繰返し演算子と併用すると、xの方が. より優先度が高いので、*+の関係のようにx側の部分式が先に評価されます。

繰返し演算子

編集

x は、繰返し演算子です。 Perlにしては珍しく、オペランドによって演算内容と返す型が変わります。

#!/usr/bin/perl
use v5.30.0;
use warnings;

say "ABC" x 2;
say "ABC" x 3.7; # 右辺に浮動小数点数を与えても、整数として評価されます。
#say "ABC" x -5; # XXX: 右辺に負数を与えると ーー> Negative repeat count does nothing
#say 2 x "ABC";  # XXX: 右辺に数値以外を与えると ーー> Argument "ABC" isn't numeric in repeat (x)

my @ary = qw(abc def ghi);
say "@{[ @ary x 9 ]}";           # 右辺が数値の場合、左辺は文字列に型強制され配列は要素数を文字列化されます。
say "@{[ 9 x @ary ]}";           # 数値 x 配列は、配列の要素を数値に置換えた文字列(配列ではありません)を返します。 
say "@{[ qw(1 2 3 4) x @ary ]}"; # 配列左 x 配列右は、配列右の要素を配列左に置換えた配列を返します。
実行結果
ABCABC
ABCABCABC
333333333
999 
1 2 3 4 1 2 3 4 1 2 3 4

クオート演算子

編集

q/STRING/は、文字列リテラルを表します。 変数と式の展開は、行なわれません。 (シングルクオーテーション)で囲まれた文字列リテラルに相当しますが、\(バックスラッシュ)でエスケープする必要はありません。 \の変換規則は、下記の実行結果のように変則的です。

q/STRING/
use v5.30.0;

my $c = 'I\'m fine.';
my $d = q(I'm fine.);
say $c eq $d ? "一致" : "不一致";

say q(1: I\'m fine.);
say q(2: I\\'m fine.);
say q(3: I\\\'m fine.);
say q(4: I\\\\'m fine.);
実行結果
一致
1: I\'m fine.
2: I\'m fine.
3: I\\'m fine. 
4: I\\'m fine.

qq/STRING/は、文字列リテラルを表します。 変数と式の展開が、行なわれます。 "(ダブルクオーテーション)で囲まれた文字列リテラルに相当しますが、をエスケープする必要はありません。 \の変換規則は、下記の実行結果のように変則的です。

qq/STRING/
use v5.30.0;

my $x = "Hello, \"$^V\"!";
my $y = qq(Hello, "$^V"!);
say $x;
say $y;

say qq(1: \");
say qq(2: \\");
say qq(3: \\\");
say qq(4: \\\\");
実行結果
Hello, "v5.30.0"!
Hello, "v5.30.0"!
1: "
2: \"
3: \" 
4: \\"

qw/STRING/は、空白および改行で区切られた文字列を、文字リテラルを要素とするリストを表します。 変数と式の展開は、行なわれません。 対応する他のリテラル表現がありませんが、概ね

qw/STRING/
split(" ", q/STRING/)
に相当しますが、厳密には qw/STRING/は、コンパイル時に実際のリストを生成し、スカラーコンテキストではリストの最後の要素を返します。
qw/STRING/
use v5.30.0;

my $x = qw(a bc def);
my @y = qw(a bc def);
my $z = @y;

say "\$x --> $x";
say "\@y --> @y";
say "\$z --> $z"
実行結果
$x --> def
@y --> a bc def 
$z --> 3

よくある間違えとしては、セパレーターとして ,(カンマ)を使ってしまったり、(複数行のqw/STRING/で)#(井桁)をコメントになると期待してしまうことです。 これは、use warnings;か、use v5.36.0; warnings プラグマを有効にすることで警告を受けます(Perl5.36.0以降は warnings プラグマが標準で有効で、無効にするには no warnings; とします)。

qw/STRING/(警告あり)
use v5.30.0;
use warnings;

my @x = qw(a,bc,def);
my @y = qw@
Hello world!     # world
Hello perl!      # perl
Hello universe!  # universe
@;

say "\@x --> @x";
say "\@y --> @y";
コンパイル結果
Possible attempt to separate words with commas at Main.pl line 4. 
Possible attempt to put comments in qw() list at Main.pl line 9.
実行結果
@x --> a,bc,def 
@y --> Hello world! # world Hello perl! # perl Hello universe! # universe

正規表現リテラルの一般化

$x = qr/^Regexp$/i;

バッククォートリテラルの一般化

$x = qx/uname -a/;   # `uname -a`; と同じ

ヒアドキュメント

編集

行指向のクォートの形式は、シェルのヒアドキュメント構文に基づくものです。 << の後に引用を終了する文字列を指定すると、現在の行から終了文字列までのすべての行が、その項目の値となります。

終了文字列の前に ~ を付けると、「インデント付きHere-docs」を使用することを指定します。

終了文字列は、識別子(単語)か、引用符で囲まれたテキストのどちらかです。 引用符で囲まれていない識別子は二重引用符のように機能します。 <<と識別子の間には、識別子が明示的に引用されていない限り、スペースを入れてはいけません。 終端文字列は,終端行に単独で (引用せず,周囲に空白を入れずに) 表示されなければなりません。

終了文字列が引用されている場合、使用される引用符の種類によって、そのテキストの扱いが決まります。

ダブルクォーテーションマーク "
通常のダブルクォーテーションマークで囲まれた文字列と全く同じ規則でテキストが補間されることを示します。
シングルクォーテーションマーク '
テキストがその内容を補間することなく、文字通りに扱われることを示します。これは、バックスラッシュが特別な意味を持たないことを除けば、一重引用符で囲まれた文字列と同様です。
シェルと同様、<<に続くバックスラッシュ付きの単語は、シングルクォート文字列と同じ意味を持ちます。
バッククォーテーションマーク `
文字列がバッククォーテーションマークで埋込まれている場合と同じように扱われます。すなわち、内容は二重引用符で囲まれているかのように補間され、シェル経由で実行され、その実行結果が返されます。
クォーテーションマークなし
(ダブルクォーテーションマークに同じ)
ヒアドキュメント
my ($world, $perl) = qw(World Perl);

print <<EOS;
Hello\t$world!
Hello\t$perl!
@@@
EOS

print <<"EOS";
Hello\t$world!
Hello\t$perl!
@@@
EOS

print <<'EOS';
Hello\t$world!
Hello\t$perl!
@@@
EOS

print <<`EOS`;
uname
uname -a
cat /etc/debian_version
EOS
実行結果
Hello	World!
Hello	Perl!
@@@
Hello	World!
Hello	Perl!
@@@
Hello\t$world!
Hello\t$perl!
@@@
Linux
Linux 55179a8a049f 5.15.0-1017-aws #21~20.04.1-Ubuntu SMP Fri Aug 5 11:44:14 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux
bullseye/sid
この例では、終了文字列を EOS としましたが、EOFなどもよく使われます。
ヒアドキュメントは入れ子にできるので、入れ子レベルに応じた名前を付けることになると思います。


ビット列演算子

編集

任意のサイズのビット列( Bitstring )は、ビット演算子(~ | & ^)で操作することができる。

ビットごとの否定を返します。

ビットごとの論理和(or)を返します。

ビットごとの論理積(and)を返します。

ビットごとの排他的論理和(xor)を返します。

文字列強制版ビット列演算子

編集

オペランドを文字列に強制するバージョンのビット演算子(~. |. &. ^.)です。

オペランドを文字列に強制し、ビットごとの否定を返します。

オペランドを文字列に強制し、ビットごとの論理和(or)を返します。

オペランドを文字列に強制し、ビットごとの論理積(and)を返します。

オペランドを文字列に強制し、ビットごとの排他的論理和(xor)を返します。

シフト演算子

編集

右ビットシフトを行います。

左ビットシフトを行います。

論理演算子

編集

論理演算子は、典型的にはif文などの条件式に使用されますが、短絡評価を行うため制御構造としても機能します。また、||or&&and!not は別名関係にありますが、or,and,notの方が優先順位が低いことに注意してください。"単語よりも演算子らしい記号のほうが強い"と覚えておいてください。

||は、論理和を返す二項演算子です。

use v5.30.0;
use warnings;

my @v = ( 0, 5, !!0, "NaN" ),;
foreach my $x (@v) {
    foreach my $y (@v) {
        say "$x || $y --> @{[ $x || $y ]}";
    }
}
実行結果
0 || 0 --> 0
0 || 5 --> 5
0 ||  --> 
0 || NaN --> NaN
5 || 0 --> 5
5 || 5 --> 5
5 ||  --> 5
5 || NaN --> 5
 || 0 --> 0
 || 5 --> 5
 ||  --> 
 || NaN --> NaN
NaN || 0 --> NaN
NaN || 5 --> NaN
NaN ||  --> NaN 
NaN || NaN --> NaN
0, 5, 真理値偽, 非数の組合わせを試しました。
論理和演算子は、名前と違い真理値ではなくスカラーを返します。
$x || $y
$x ? $x : $y
と等価です

or は、優先度が低いバージョンの || 演算子です。

短絡評価

編集

論理和は左引数が偽である場合のみ右引数の評価を行います。 このような論理演算子の実質的に制御構造としての振る舞いを「短絡評価」とよびます。 論理和はまた、最後に評価された値を返すので例外処理にも使われます。 このとき or の優先度が低いことが役に立ちます。

$success or die;
これは、「成功または死ぬ」あるいは「成功するか死ぬか」と読めます。

&&は、論理積を返す二項演算子です。

use v5.30.0;
use warnings;

my @v = ( 0, 5, !!0, "NaN" ),;
foreach my $x (@v) {
    foreach my $y (@v) {
        say "$x && $y --> @{[ $x && $y ]}";
    }
}
実行結果
0 && 0 --> 0
0 && 5 --> 0
0 &&  --> 0
0 && NaN --> 0
5 && 0 --> 0
5 && 5 --> 5
5 &&  --> 
5 && NaN --> NaN
 && 0 --> 
 && 5 --> 
 &&  --> 
 && NaN --> 
NaN && 0 --> 0
NaN && 5 --> 5
NaN &&  -->  
NaN && NaN --> NaN
0, 5, 真理値偽, 非数の組合わせを試しました。
論理積演算子は、名前と違い真理値ではなくスカラーを返します。
$x && $y
!$x ? $x : $y
と等価です

and は、優先度が低いバージョンの && 演算子です。

論理積も短絡評価を行います。

この // は、正規表現のそれではなく / 2文字からなるトークンで、|| とよく似ていますが、左辺が定義さていれば左辺を、定義されていなければ右辺を返します。オプショナルな引数の定義状況のテストを意図されています。

my $x = defined $opt ? $opt : "no";

my $x = $opt // "no";

と簡素に書くことができます。

// は、5.10 で追加されました。

notは、与えられた論理式の否定を表します。Aが真のとき、not A は偽です。Aが偽のとき、not A は真です。

$x = 2;
if (not $x == 5 ) {
  say "実行された";
}
実行結果
実行された
解説
$x = 2
2 == 5 ⇒ 偽
not 偽 ⇒ 真
∴ not $x == 5 は真
if の条件が真なので、コードブロック実行され say が実行されます。

! は、優先度が高いバージョンの not 演算子です。

数値比較演算子

編集

不等号を表すのに利用します。

if ($x > $y) {
   #この部分は$xが$yより大きいときに実行されます
}

if ($x <= $y) {
 #この部分は$xが$y以下のときに実行されます
}
以上または以下の <= や >= については、最初に不等号の記号が来ます。(Perl にかぎらずC言語など他のプログラム言語でも、同様の順序です。)

比較演算子は数値の他、文字列にも 数学記号の ≦ と <= は同じ意味ですが、パソコンの直接入力(半角英数)には ≦ が無いので、プログラミングでは <= で代用されます。 これは、Cも同様です(PerlがCを模倣したのですが)。 Fortranの様にASCIIコードが制定される前の言語では '<' がキャラクターセットになかったり文字のサポートがまちまちだったので、.EQ.,.NE.,.GT.,.LT.,.GE.,.LE.,.AND.,.OR.,.NOT. のように演算子の頭文字をドット. で囲み表現しました。 Perlの文字列の比較演算子も概ねFortranの記法にならっています。

同じ数値であることや、違う数値であることを表すのに使用されます。両辺の変数などの内容を(文字列ではなく)数値として評価します。

if ($x == $y) {
     # この部分は$xが$yと同じ値のときに実行されます
}

if ($x != $y)) {
     # この部分は$xが$yと違う値のときに実行されます
}

== は、両辺の値が等しい事を要求します。if文の中でよく使います。

(Perlに限らずC言語などでも、)よくあるミスで、「=」と記号をひとつだけにするミスがありますが、これはエラーになるか、または代入演算子として解釈されるのでバグになります。

!= は 両辺の値が等しくない事を要求します。つまり、!= は両辺の値が違っている事を要求します。

等しくない場合の != では、否定の記号 ! が先に来ます。(Perl にかぎらずC言語など他のプログラム言語でも、同様の順序です。)

左右の数値の大小関係により -1, 0, 1 のいずれかを返します。これは主にsortで使われます。

@a = (22, 3, 111);

@a = sort {$a <=> $b} @a;
# この時点で@aは (3, 22, 111) になっています

@a = sort {$b <=> $a} @a;
# この時点で@aは (111, 22, 3) になっています

文字列比較演算子

編集
文字列比較演算子
演算子 意味
A eq B AとBは等しい
A ne B AとBは等しくない
A gt B AはBより大きい
A ge B AはB以上
A lt B AはBより小さい
A le B AはB以下

両辺の文字列が、文字列として評価した場合に、同じ値かを調べるときに使用します。

なお、== および != は両辺が数値として評価した場合なので、意味が違います。

Perlには変数に型が無いので、C言語とは異なり、比較演算子の側で、変数の内容を数値として評価するか、内容を文字列として評価するかの指定が必要になるのです。

if ($x eq $y) {
    # この部分は$xが$yと同じ文字列のときに実行されます
}

if ($x ne 'correct')) {
    # この部分は$xに代入されている文字列が 'correct' でなかったときに実行されます
}

eq は、両辺を文字列として比較したときに、両辺が同じであることを要求します。

なお「eq」とは equal (等号、等しい)の略であるとされる。

ne は、両辺を文字列として比較したときに、両辺が異なることを比較します。

「ne」とは not equal の略だとされる。


二つの文字列の辞書順での大小を比較します。

if ($x le $y) {
     # この部分は$xが$y以下のときに実行されます
}

; le -- less than or equal -- 以下
; ge -- greater than or equal -- 以上
; lt -- less than -- より小さい未満
; gt -- greater than -- より大きい

二つの文字列の辞書順での大小関係により-1, 0, 1のいずれかを返します。これは主にsortで使われます。

@a = ('3', '22', '111', 'z', 'A', 'a', 'Z');

@a = sort {$x cmp $y} @a;
# この時点で@aは ('111', '22', '3', 'A', 'Z', 'a', 'z') になっています

@a = sort {$y cmp $x} @a;
# この時点で@aは ('z', 'a', 'Z', 'A', '3', '22', '111') になっています

条件演算子

編集
条件 ? 式1 : 式2
条件が成立する場合は式1の値を、そうでない場合は式2の値を返します。
$x = 1;
$y = $x ? 'true' : 'false';      # $y には 'true' が代入される

範囲演算子

編集
式1 .. 式2 で範囲を表します。
式1・式2はリテラルである必要はありません。
リストコンテキストの例
#!/usr/bin/env perl
use v5.30.0;
use warnings;

print<<EOS;
1..32 --> @{[ 1..32 ]}
"1".."12" --> @{[ "1".."12" ]}
'a'..'z' --> @{[ 'a'..'z' ]}
'A'..'BE' --> @{[ 'A'..'BE' ]}
'A1'..'D7' --> @{[ 'A1'..'D7' ]}
EOS
実行結果
1..32 --> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
"1".."12" --> 1 2 3 4 5 6 7 8 9 10 11 12
'a'..'z' --> a b c d e f g h i j k l m n o p q r s t u v w x y z
'A'..'BE' --> A B C D E F G H I J K L M N O P Q R S T U V W X Y Z AA AB AC AD AE AF AG AH AI AJ AK AL AM AN AO AP AQ AR AS AT AU AV AW AX AY AZ BA BB BC BD BE 
'A1'..'D7' --> A1 A2 A3 A4 A5 A6 A7 A8 A9 B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 D0 D1 D2 D3 D4 D5 D6 D7
1..32は、(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32)に展開されます
"1".."12"は、文字列ですが数値として解釈可能なので(1,2,3,4,5,6,7,8,9,10,11,12)に展開されます
'a'..'z'は、マジカルインクリメントで('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z')に展開されます
'A1'..'D7'も、マジカルインクリメントですが'A9'から'B0'に桁上りしています


スカラーコンテキストの例
#!/usr/bin/env perl
use v5.30.0;
use warnings;

while (<DATA>) {
    print if 8 .. 10;        # 8行目から10行目を出力
    print if /<head>/ .. /<\/head>/; # head要素の内容を出力
}
__DATA__
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset='utf-8'>
    <title>簡単な例</title>
  </head>
  <body>
    <h1>簡単な例</h1>
    <p>この文書は非常に簡単な例です</p>
  </body>
</html>
実行結果
  <head>
    <meta charset='utf-8'>
    <title>簡単な例</title>
  </head>
    <h1>簡単な例</h1>
    <p>この文書は非常に簡単な例です</p> 
</body>
スカラーコンテキストの場合はフリップフロップ演算子となります。
オペランドが数値の場合は、暗黙に$.(行番号)と比較 (==) を行います。
...演算子を用いた場合は、左オペランドが真となった後、次の行に移ってから右オペランドの評価を行う。

フリップフロップ演算子

編集

フリップフロップ演算子として .. が振る舞うときは癖が強いです

$n == 3 .. $n == 5 ? "1" : "2"
3 <= $n <= 5 ? "1" : "2"

と等価です。

    print if 8 .. 10;        # 8行目から10行目を出力
    print if $. == 8 .. $. == 10;        # 8行目から10行目を出力
と等価で
    print if 8 <= $. <= 10;        # 8行目から10行目を出力

とも等価です。

置換演算子

編集
s/PATTERN/STRING/

PATTERNにマッチするものをSTRINGに置換します。PATTERNは正規表現です。

$str = "Wiki";
$str =~ s/(Wiki)/$1pedia/;
print $str; # Wikipedia

パターン変換演算子

編集

tr/PATTERN1/PATTERN2/ 1文字を対応する1文字に置換します。PATTERNには正規表現ではなく、文字クラス(角括弧で囲まれた文字クラスの[]の内側)を指定します。

tr///
use v5.30.0;
use warnings;

my $str = "ABC BCA CAB";
$str =~ tr/ABC/012/;
say $str;

$str =~ tr/012/AB/;
say $str
実行結果
012 120 201 
ABB BBA BAB
PATTERN1とPATTERN2の長さが違っても(use warnings; しても)警告されず、およそ期待とは違う結果になります。
変換結果に疑問があったら、まずパッターン同士の長さの不一致を疑いましょう。

ハイフンを使って範囲指定を行うことができます。

範囲指定を使ったパターン変換
use v5.30.0;
use warnings;

my $str = "WIKIBOOKS";
$str =~ tr/A-Z/a-z/;
say $str
実行結果
wikibooks

=~ を使わないと $_ の変換対象になり、変換した文字数を返します。

変換した文字数
use v5.30.0;
use warnings;

my $str = "WIKIBOOKS";
local $_ = $str;
say tr/[A-Z]/[A-Z]/;
実行結果
9

リファレンス参照演算子

編集

->は、中置のデリファレンス演算子で、左辺のリファレンスに対し、右辺のフォームによりそれぞれ

[...]
配列
{...}
ハッシュ
(...)
サブルーチン

を参照します。

パターン変換演算子

編集

y/PATTERN1/PATTERN2/ tr///の同義語です。

秘密の演算子

編集

Perl には、秘密の演算子( secret operators )と呼ばれる一連の独特の記法があります[5]。 これらは実際には演算子ではないのですが、高い頻度でコード上に登場するので愛称がつけられたものです。

Perl の秘密の演算子
演算子 愛称 和訳 機能
0+ Venus ビーナス 数値に強制変換
@{[ ]} Baby cart べピーカー リストの展開
!! Bang bang バンバン 論理値化
}{ Eskimo greeting エスキモーの挨拶 ワンライナーでの END ブロック
~~ Inchworm 尺取り虫 スカラーコンテキスト
~- Inchworm on a stick 串刺し尺取り虫 高優先順序のデクリメント
-~ Inchworm on a stick 串刺し尺取り虫 高優先順序のインクリメント
-+- Space station 宇宙ステーション 高優先順序の数値化
=( )= Goatse 山羊 スカラー/リストコンテキスト
=< >=~ Flaming X-Wing 炎上Xウィング マッチ入力、キャプチャの割り当て
~~<> Kite 一行入力
<<m=~m>> m ; Ornate double-bladed sword デコデコした両刃剣 複数行コメント
-=! -=!! Flathead フラットヘッド 条件デクリメント
+=! +=!! Phillips フィリップス 条件インクリメント
x=! x=!! Pozidriv プラスドライバー 条件により '' にリセット
*=! *=!! Torx 星型ドライバー 条件により 0 にリセット
,=> Winking fat comma ウインクする太っちょコンマ non-stringifying fat comma
()x!! Enterprise USSエンタープライズ 論理リスト スカッシュ
0+!! Key to the truth 真理の鍵 数値論理値変換
~~!! Serpent of truth 真理のサーペント 数値論理値変換
||() Abbott and Costello アボットとコステロ リストから偽のスカラーを削除
//() Leaning Abbott and Costello 傾いたアボットとコステロ リストから undef のスカラーを削除

ビーナス演算子は、式を強制的に数値化します。

0+
foreach $x(qw(13b 3.1415926536 1.8e3 0xff 0177 0o177 0b10110), "32 Yen", "one penny", []) {
    print "0+$x -> @{[0+$x]}\n"
}
実行結果
0+13b -> 13
0+3.1415926536 -> 3.1415926536
0+1.8e3 -> 1800
0+0xff -> 255
0+0177 -> 177
0+0o177 -> 0
0+0b10110 -> 0
0+32 Yen -> 32
0+one penny -> 0 
0+ARRAY(0x558699694470) -> 94036587791472
0+ Venus は、式を数値に強制変換します。
加算演算子 + は、両辺を数値に変換してから加算します。
左辺を0に固定したので、単純な右辺の数値への強制変換になります。
秘密の演算子の中では、一番わかりやすく実用価値も高いです。
ただ、やはり組込み関数の int などを使ったほうが、意図がわかりやすく grep 性も高くなります。

Baby cart

編集

ベビーカー演算子は、文字列の内部でリスト補間を行います。リスト項目は、$"の値で区切られます。

@{[ ]}
%engines = (
    "Thomas" => 1,
    "Edward" => 2,
    "Henry" => 3,
    "Gordon" => 4,
    "James" => 5,
);
print <<EOS;
%engines
@{[ %engines ]}
@{[ sort keys %engines ]}
@{[ sort values %engines ]}
EOS
実行結果
%engines
Thomas 1 Gordon 4 Edward 2 Henry 3 James 5
Edward Gordon Henry James Thomas 
1 2 3 4 5
@{[ ]} Baby cart は、まず内側の [ ]で匿名配列のリファレンスを作り、直後にその値を @{ }でデリファレンスしています。
@{[ ]} Baby cart は、式を文字列化します。
@{[ ]} Baby cart は、また書き換え可能なコピーを作り出せます。

[TODO:例]



関数

編集

関数とサブルーチン

編集

Perlにおいて、「関数」と「サブルーチン」という用語は異なる意味で使われます。まず、「関数」という言葉は主にPerlが提供する組み込み機能、つまり組込み関数を指します。たとえば、printchomplengthといった関数がこれに該当します。これらはPerlの言語仕様にあらかじめ用意されたキーワードであり、特別な処理が施されています。たとえば、printはリスト全体を引数として受け取り、文法的にも他の部分と衝突することなく優先的に認識されます。一方で、組み込み関数は\&を使ってコードリファレンスを取得することができず、別のサブルーチンや無名サブルーチンを使ってラップする必要があります。

これに対して、「サブルーチン」は、ユーザーがsubキーワードを用いて定義するコードブロックを指します。サブルーチンは自由に作成・変更でき、自分で名前を付けて定義することが可能です。たとえば、sub greet { return "Hello, World\n"; }のようにして、独自のサブルーチンを定義できます。そして、このサブルーチンは\&greetのようにコードリファレンスとして取得できるため、他のサブルーチンに引数として渡したり、後から実行したりすることができます。

呼び出し方についても違いがあります。たとえば、組み込み関数とサブルーチンのどちらも引数がない場合には括弧()を省略できます。しかし、サブルーチンの場合には()を省略すると、構文が曖昧になる可能性があるため、可読性の観点から括弧を付けることが推奨されます。一方で、組み込み関数はキーワードとして特別に扱われるため、括弧を省略しても曖昧さが生じにくい設計になっています。

組み込み関数とサブルーチンにはこうした違いがありますが、両者を混在させて使う際には注意が必要です。特に、サブルーチンと同名の組み込み関数を定義すると、意図せず組み込み関数が優先される場合があります。これを避けるためには、名前の付け方や呼び出し方に注意する必要があります。

また、組み込み関数を他のサブルーチンに引数として渡したい場合には、直接渡すことはできないため、無名サブルーチンを用いてラップするのが一般的です。このように、Perlでは「関数」と「サブルーチン」が明確に区別され、それぞれ特有のルールや扱い方があることを理解することが重要です。

組込み関数

編集

Perlの言語コアで予め定義されている関数のことを「組込み関数」と呼びます。printlengthsubstrなどの一般的な関数から、myusedoなどのキーワード的な構文要素に至るまで、広くPerl自身に組み込まれた関数がこれに当てはまります。

基本的な関数

編集

print関数

編集
機能
print関数は、引数で与えられた文字列や文字列のリストを標準出力に出力します。引数が与えられなかったときは $_ が出力されます。
use v5.30.0;
use warnings;

print "Hello, World\n";
print "Hello, Perl\n"
実行結果
Hello, World 
Hello, Perl
print関数は、行末で改行しないので、もし改行をしたい場合には明示的にエスケープシーケンス \n を文字列の末尾に加えます。

say関数

編集

Perl 5.10 から導入されたsay 関数は、行末で改行を行います。これで、都度 \n を文字列末に記述する手間が省けます。

組込み関数 say
use strict;
use warnings;
use utf8;
binmode STDOUT,":encoding(UTF-8)";

use feature "say";
use feature ':5.10';
use v5.10;

say "Hello";
say "Yes!";
say "Boodbye";

my $message = "こんにちは";
say $message;
say を使うには、6-8 行目の use 宣言のいずれか1つが必要です。
use feature "say";
use feature ':5.10';
use v5.10;
  1. use feature "say";
    
    say を名指しで有効化しています。お勧めです。
    1. use feature qw(say switch);
      
      の様に2つ以上を列挙することもできます。
  2. use feature ':5.10';
    
    バージョン 5.10 以降の機能を全て有効にします。手早く動かすためにはいいのですが過剰です。
  3. use v5.10;
    
    意味的には上と同じですが、より簡素です。多分一番多く使われています。
CORE::say
#!/usr/bin/perl
use strict;
use warnings;

CORE::say "Hello world!";
CORE::を前置するとプラグマを使わずに say関数を使うことができます。
ワンライナーや書き捨てのスクリプトに向いています。
CORE はPerlコアルーチンの名前空間です。

文字列に変数や式を埋込む

編集

Perlでは、文字列の中に変数や式を埋め込むことができ、テンプレート言語であるかのような使いかたが出来ます。

length は文字列の長さを返します。
文字列に変数や式を埋込む
use v5.30.0;
use warnings;

my $x = "aeiou";
my $tmp = length $x;
say "length \"$x\" -> $tmp";
say "length \"aeiou\" -> @{[length 'aeiou']}";
say qq(length "aeiou" -> @{[length 'aeiou']});
実行結果
length "aeiou" -> 5
length "aeiou" -> 5 
length "aeiou" -> 5
この様に、(ダブルクォーテーションマーク)に囲まれた文字列の中では $変数 で式の値が、@{[式]} で式の値が文字列に埋込まれます。
厳密に解説するには、スカラーコンテキストとリストコンテキストの説明が必要なのですが、リファレンスなどの説明が必須なので、機会を見て一括して解説します。
qw// 演算子を使うと、変数や式が展開する文字列の中で
(ダブルクォーテーションマーク)ではなく、(シングルクォーテーションマーク)で囲まれた文字列では、変数や式は展開されません。

数学関数

編集
基本的な数学関数
編集

平方根などの数学計算をする関数が用意されています。

最小のピタゴラス数
use v5.20.0;
use warnings;

say "sqrt(3**2 + 4**2) --> @{[sqrt(3**2 + 4**2)]}";

use POSIX "hypot";

say "hypot(3, 4) --> @{[ hypot(3, 4) ]}"
実行結果
sqrt(3**2 + 4**2) --> 5 
hypot(3, 4) --> 5
Perlの組込み関数 sqrt を使って自乗和の平方根を求めています。
自乗は結果がオーバーフローあるいはアンダーフローを起こす可能性があるので、対策された hypot を使うのが定石です。
ですが、Perlの組込み関数にもMathモジュールにも hypot はなく、POSIXモジュールにあります。
この場合、use POSIX "hypot";ではなくuse POSIX;で充分なのですが、POSIXからhypotを持ってきている意外性を伝えるため明示しました。
呼出し側で、POSIX::hypot(3, 4) とするのも刺激的ですが、複数箇所あると鬱陶しいので use 側で対処しました。
hypot.pl
編集

桁あふれ対策と可変引数に対応したPerl版hypotの例。

hypot.pl
use v5.30.0;
use warnings;

use POSIX;

sub hypot {
    my ( $max, $s ) = ( 0, 0 );

    # 最大値を決定する
    for my $n (@_) {
        next      if $n == 0;
        return $n if $n != $n;       # for NaN
        my $arg = abs($n);
        return $arg if $arg == "Inf";# for Inf
        $max = $arg if $max < $arg;
    }

    # 最大値に基づいてスケーリングしつつ加算
    for my $n (@_) {
        next      if $n == 0;
        $s += ($n / $max) ** 2;  # 最大値でスケーリングして加算
    }

    # 結果を返す
    return $max * sqrt($s);
}

if ( $0 eq __FILE__ ) {
    foreach my $i ( -1075 .. -1073, -540 .. -538, 0 .. 2, 508 .. 511, 1021 .. 1024 ) {
        my $j = 2**$i;
        my ( $n, $m ) = ( 3 * $j, 4 * $j );
        say "$i: @{[ 5 * $j ]} @{[ sqrt($n*$n + $m*$m) ]} @{[ ::hypot($n, $m) ]} @{[ POSIX::hypot($n, $m) ]}";
    }
}
実行結果
-1075: 0 0 0 0
-1074: 2.47032822920623e-323 0 2.47032822920623e-323 2.47032822920623e-323
-1073: 4.94065645841247e-323 0 4.94065645841247e-323 4.94065645841247e-323
-540: 1.38922421842817e-162 0 1.38922421842817e-162 1.38922421842817e-162
-539: 2.77844843685635e-162 3.14345556940526e-162 2.77844843685635e-162 2.77844843685635e-162
-538: 5.55689687371269e-162 5.44462475754526e-162 5.55689687371269e-162 5.55689687371269e-162
0: 5 5 5 5
1: 10 10 10 10
2: 20 20 20 20
508: 4.18993997810706e+153 4.18993997810706e+153 4.18993997810706e+153 4.18993997810706e+153
509: 8.37987995621412e+153 8.37987995621412e+153 8.37987995621412e+153 8.37987995621412e+153
510: 1.67597599124282e+154 Inf 1.67597599124282e+154 1.67597599124282e+154
511: 3.35195198248565e+154 Inf 3.35195198248565e+154 3.35195198248565e+154
1021: 1.12355820928895e+308 Inf 1.12355820928895e+308 1.12355820928895e+308
1022: Inf Inf Inf Inf
1023: Inf Inf Inf Inf 
1024: Inf Inf Inf Inf
Perlには、Cの isnan() や isfinite() に相当する関数がないので、それぞれ $n != $nabs($n) == "Inf" としました。
POSIXモジュールにはisfinite関数があるので、それを使えばよいのですが、POSIX::hypotの代替実装なので利用を見送りました。
三角関数など
編集

sin,cos は組込み関数にありますが、tan, acos など他の三角関数や円周率(pi)を使用するには、use宣言を使って Math::Trigモジュールから導入します。

余弦関数と逆余弦関数
use 5.30.0;
use warnings;

use Math::Trig qw(pi acos);

say "cos(pi) -> cos(@{[pi]}) -> @{[cos(pi)]}";
say "acos(-1) ->  @{[acos(-1)]}"
実行結果
cos(pi) -> cos(3.14159265358979) -> -1 
acos(-1) ->  3.14159265358979
円周率は、Math::Trigモジュールを導入すると使えるようになりますが、$piではなく piです。
文字列中で参照する場合は "@{[pi]}" となります。
Perlの三角関数の角度の単位は多くのプログラミング言語同様ラジアン(弧度法)です。
正弦sinと余弦cosはPerlの言語コアで定義されていますが、正接tanはMath::Trigモジュールで定義されています。
Math::Trigモジュールでは、piなどの定数や他の三角関数関連の諸関数が定義されています。

日付時刻関係の関数

編集

現在の日時や時刻などを表すには、time関数およびlocaltime関数を使います。

エポックからの秒数と、ローカル時刻
use v5.30;
use warnings;
use utf8;
binmode STDOUT,":encoding(UTF-8)";

my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time());  
say "time() -> @{[time()]}";
say "いまは、@{[1900 + $year]} 年 @{[1 + $mon]} 月 $mday 日 $hour 時 $min 分 $sec 秒です。";

use POSIX "strftime";
say strftime "%Y/%m/%d %H:%M:%S", localtime();
say strftime "%c", localtime();
実行結果
time() -> 1668851859
いまは、2022 年 11 月 19 日 9 時 57 分 39 秒です。
2022/11/19 09:57:39 
Sat 19 Nov 2022 09:57:39 AM UTC
説明
time関数は、エポック(1970年1月1日0時0分0秒(UTC) )からの通算秒を返します。
localtime関数は、エポックからの通算秒形式の引数を、年月日時分秒の要素に分解しリストで返します。
localtime関数は、引数を省略すると time()が仮定されるので、この例での引数は冗長です。
localtimeが返すリストを操作するには1900を足したり月数の補正をしたり面倒です(よく間違えます)。
POSIXモジュールの strftime を使うと、Cのstrftime()と同じ(正確にはPOSIXと同じ)書式化文字列がつかえ可読性も向上します。使いましょう。
DateTimeモジュールもあるのですが、Perl流のオブジェクト指向の構文で書かれているので、直感的とは言い難いコードになります。使うなとまでは言いません。

split関数

編集

split関数には、与えられたパターンで文字列を区切り、リストで返します。

split
use v5.30;
use warnings;
use utf8;
binmode STDOUT,":encoding(UTF-8)";

my @list = split(/ /, '睦月 如月 弥生 卯月 皐月 水無月 文月 葉月 長月 神無月 霧月 師走');
for (my $i = 0; $i <= $#list; $i++){
  say qq(@{[$i+1]}月: $list[$i]);
}
実行結果
1月: 睦月
2月: 如月
3月: 弥生
4月: 卯月
5月: 皐月
6月: 水無月
7月: 文月
8月: 葉月
9月: 長月
10月: 神無月
11月: 霧月 
12月: 師走

サブルーチン

編集

Perlでは、ユーザーが定義する関数のことをサブルーチン( subroutine )と呼び、キーワードsubを使い定義します。

シンプルなサブルーチンの定義と呼出し

編集

サブルーチンの定義と呼出しは、説明することがほとんどないほど簡単です。

シンプルなサブルーチンの定義と呼出し
use v5.30.0;
use warnings;

sub world {
    say "Hello, World";
}

sub perl {
    say "Hello, Perl";
}

&world;
&perl;

world;
perl;
実行結果
Hello, World
Hello, Perl
4-6がサブルーチンworldの定義
8-10がサブルーチンperlの定義
見たままです
12,15 がサブルーチンworldの呼出し
13,16 がサブルーチンperlの呼出し
見たままですが、&が前置されていなくても、されていても同じというのは釈然としません。
この & は、組込み関数では前置できません。
というわけで、& を関数呼出しで前置するのは、「組込み関数ではなくサブルーチンを呼んでいます」という意味になります。
また、& を省略するとサブルーチンの宣言より前に、サブルーチンを呼出すことはできません。

サブルーチン宣言

編集

サブルーチンの定義より先にサブルーチンを呼出す必要があることがあります(典型的には、お互いに呼び合う関数)。 この場合は、呼出ごとに & を前置するか、サブルーチン宣言をサブルーチン呼出の前にします。

サブルーチン宣言
use v5.30.0;
use warnings;

&world;
&perl;

sub world;
world;

sub perl;
perl;

sub world {
    say "Hello, World";
}

sub perl {
    say "Hello, Perl";
}
実行結果
Hello, World
Hello, Perl
Hello, World 
Hello, Perl
4,5は & を前置しているので、宣言がなくてもサブルーチンとわかる。
7,10がサブルーチン宣言で、サブルーチン定義の前方参照を解決します。

グローバル変数を使ったサブルーチンの振る舞いの変更

編集

前出の例は、ほとんど同じ内容のサブルーチンを2つ用意しましたが、1つにまとめてみましょう。

グローバル変数を使ったサブルーチンの振る舞いの変更
use v5.30.0;
no strict;
use warnings;

$who = "WHO!";

sub hello {
    say "Hello, $who";
}

&hello;
$who = "world";
&hello;
$who = "Perl";
&hello;
実行結果
Hello, WHO!
Hello, world 
Hello, Perl
グローバル変数 $who を使ってメッセージの後半を変えています。
この方法はこの方法で動きますし、かつてのFORTRANやBASICは、まさにこのようにグローバル変数でサブルーチンをコントロールしていました。
しかし、2行目のno strict;で明示的に strict を無効にしなければエラーが出るほど、グローバル変数の使用は推奨されない方法です。

引数を使ったサブルーチンの振る舞いの変更

編集

前出の例は、グローバル変数を使っていましたが、グローバル変数はデーターフロー的なスパゲティーコードに直結するので、引数を使ってスマートに実装してみましょう。

引数を使ったサブルーチンの振る舞いの変更
use v5.30.0;
use warnings;

sub hello {
    my $who = shift;
    $who //= "WHO?!";
    say "Hello, $who";
}

&hello();
&hello("world");
&hello("Perl");
実行結果
Hello, WHO?!
Hello, world 
Hello, Perl
引数の受け取り
my $who = shift;
Perlのサブルーチンの引数は、名前を持った仮引数ではなく特殊変数 @_ に配列として渡されます。
第一引数が $_[0] となります。
半ば定型文なのですが、引数を左から順に読む動作が shift(shift @_ の意味) と符合するので、それらしい名前(この場合は $who)の変数を宣言し shift で初期化するコードが良く見られます。
キーワード my を前置して宣言した変数は、「レキシカル変数」となり、サブルーチン(この場合は hello)を抜けると参照できなくなり、もう一度同じサブルーチンを呼んでも、もう違う値になってます(非永続的なレキシカルスコープ)。
ディフォルト引数
$who //= "WHO?!";
Perlには、ディフォルト引数の構文はなかったので、引数が渡されなかった場合の既定値(ディフォルト)を指定するには、このようなイディオムになります。

この @_ による引数の受渡しは、Perlでは約20年に渡って使われてきましたが、他のプログラミング言語のように名前付きの仮引数が欲しいとの要望は根強く、シグネチャーとしてv5.20.0から実験的な機能として実装されています。

戻値と再帰

編集

ここまでで、引数を受取りサブルーチンの振舞いを変えることができるようになりました。 次に、「値を返す手段」が問題になります。 グローバル変数を使って値を返せそうですが「データーフロー的なスパゲティーコード」になるのでサブルーチンの「戻値」を使ってみましょう。

戻値を返すサブルーチン

編集

いままでのサブルーチンは値を返しませんでしたが、Perlのサブルーチンは値を1つ返すことができます。

戻値を返すサブルーチン
use strict;
use warnings;

sub add {
    my ($x, $y) = @_;
    return $x + $y;
}

print("add(12, 9) -> @{[add(12, 9)]}\n");
print("add(1.2, 0.9) -> @{[add(1.2, 0.9)]}\n");
print("add(123, '89') -> @{[add(123, '89')]}\n");
実行結果
add(12, 9) -> 21
add(1.2, 0.9) -> 2.1 
add(123, '89') -> 212
戻値を返す文
return $x + $y;
Perlのサブルーチンの戻値を返す場合は
return
return  ;
での「式」の値が返ります。
もし return のないサブルーチンの戻値を参照すると、サブルーチンで最後に評価した式の値がかえります。このため
return $x + $y;
$x + $y;
と同じです。
Perl の ; は、Cのように式を文にするのではなく、式と式を区切るデリミターなので最後の式の後に ; は不要です。
戻値とは関係ありませんが、
文字列が数値に自動変換される
print("add(123, '89') -> @{[add(123, '89')]}\n");
が、何事もなかったかのように
add(123, '89') -> 212
となるように、数値が期待されす文脈に数値に変換できる文字列が来ると、自動的に数値に変換され演算されます。
Perlのこの暗黙の変換は、エポックからの通算秒が桁上りしたときなどに発現する凶悪なバグの原因になってきました。

再帰的呼出し

編集

引数と戻値が手に入ったので、再帰的呼出しを行うサブルーチンを書いてみます。

整数の冪乗
編集

整数の累乗を返すサブルーチン pow を書いてみました。

整数の冪乗
use v5.30.0;
use warnings;

sub pow {
    my ($n, $m) = @_;
    return "Domain error" if $m < 0;
    return 1 if $m == 0;
    return $n if $m == 1;
    return $n * &pow($n, $m - 1);
}

say "pow(2, $_) -> @{[ pow(2, $_) ]}" foreach -1..3;
実行結果
pow(2, -1) -> Domain error
pow(2, 0) -> 1
pow(2, 1) -> 2
pow(2, 2) -> 4 
pow(2, 3) -> 8
9 で、pow自身を指数を1減らして呼んでいます。
$n, $m が重複使用されているように見えますが、myがついているので、再帰レベルが1つ下るごとに別のインスタンスが生成されています。
整数を3桁ごとにカンマで区切って表示する
編集

整数を3桁ごとにカンマで区切って表示するサブルーチン comma3 を書いてみました。

整数を3桁ごとにカンマで区切って表示する
use v5.30.0;
use warnings;

sub comma3 {
    my $n = shift;
    return "-" . comma3(-$n) if $n < 0;
    my ($num, $rem) = (int($n / 1000), $n % 1000);
    return comma3($num) . sprintf ",%3d", $rem if $num;
    return sprintf "%d", $rem
}

say comma3 $_ foreach qw(
    123456789 
    -999999 
    0 
    1 
    12 
    123 
    1234
)
実行結果
123,456,789
-999,999
0
1
12
123
1,234
フィボナッチ数列とメモ化とベンチマーク
編集

再帰で必ず取上げられるフィボナッチ数列とメモ化を題材に、ベンチマークテストを行ってみようと思います。

フィボナッチ数列とメモ化とベンチマーク
use v5.30.0;
use warnings;

sub fibonacci {
    my $n = shift;
    return $n if $n == 0;
    return $n if $n == 1;
    return fibonacci($n - 2) + fibonacci($n - 1)
}

sub fibonacci_norec {
    my $n = shift;
    my ($x, $y) = (1, 0);
    ($x, $y) = ($y, $x + $y) foreach 1..$n;
    return $y
}

sub fibonacci_memorization {
    my $n = shift;
    state @table = (0, 1);
    return $table[$n] if defined $table[$n]; 
    return $table[$n] = fibonacci($n - 2) + fibonacci($n - 1)
}

use Benchmark qw/timethese cmpthese/;
my $i = 16;
cmpthese timethese(2 ** 10, {
  "再帰"   => sub { fibonacci($_) foreach 1..$i },
  "非再帰" => sub { fibonacci_norec($_) foreach 1..$i },
  "メモ化" => sub { fibonacci_memorization($_) foreach 1..$i },
});
実行結果
Benchmark: timing 1024 iterations of メモ化, 再帰, 非再帰...
 メモ化:  0 wallclock secs ( 0.00 usr +  0.00 sys =  0.00 CPU)
            (warning: too few iterations for a reliable count)
    再帰:  2 wallclock secs ( 1.58 usr +  0.00 sys =  1.58 CPU) @ 648.10/s (n=1024)
 非再帰:  0 wallclock secs ( 0.01 usr +  0.00 sys =  0.01 CPU) @ 102400.00/s (n=1024)
            (warning: too few iterations for a reliable count)
                           Rate              再帰         非再帰  メモ化
再帰                    648/s                  --              -99%      -100%
非再帰              102400/s              15700%                --      -100% 
メモ化 1023999999999999872/s 157999999999999968% 1000000000000001%         --
fibonacci は、素朴な再帰版のフィボナッチ数列です。
fibonacci_norec は、非再帰版のフィボナッチ数列です。
fibonacci_memorization は、メモ化を施した再帰版のフィボナッチ数列です。
20行目の state @table = (0, 1);は、非揮発性のレキシカルスコープ変数の宣言で、 my と違い最初しか初期化されず、再び同じサブルーチンを呼ばれたときには前の値を憶えています。またサブルーチンの外から参照する方法はありません。
メモ化は、一度計算した答えを記憶して次からは記憶から答える戦略なので、ベンチマークに有利です。
メモ化を行うアルゴリズムと行なわないアルゴリズムでは、ベンチマークのような繰り返しに関する感受性が違います。繰返し回数に対し線形に時間が増えないアルゴリズムはメモ化を行っている可能性があるので、ループの底で使われるのには適していますが、頻度の低い使い方の場合、性能が予想より悪い可能性があります。
このことから、実際のプログラムのプロファイル結果とベンチマークの結果の傾向の比較も重要になります。
無名再帰
編集

サブルーチン自身へのリファレンス __SUB__ を使うと無名関数の再帰ができます。

整数の冪乗
use v5.30.0;

# 階乗 n!
say (sub {
    my $n = shift;
    return $n == 0 ? $n
         : $n == 1 ? $n
         : $n * __SUB__->( $n - 1 );
}->(7));
実行結果
5040

シグネチャー

編集

前出の例で、引数を使ってサブルーチンの振る舞いを変えることができました。

機能的には充足しているのですが、名前付きの仮引数を望む声は以前からあり、Perl 5.20.0 から「実験的」( experimental )なシグネチャーの実装が行なわれ、Perl 5.26.0 からコア機能の1つとなりました。

シグネチャー
# !/usr/bin/perl
use v5.30;
use feature 'signatures';
no warnings "experimental::signatures";
 
sub hello($who = "WHO?!") {
    say "Hello, $who";
}

&hello();
&hello("world");
&hello("Perl");
実行結果
Hello, WHO?!
Hello, world 
Hello, Perl
シグネチャー
sub hello($who = "WHO?!") {
名前を持った仮引数が使えるようになりました。
ディフォルト引数にも対応しています。
Perl 5.36.0 からは、signatures は experimental を卒業したので
use v5.30;
use feature 'signatures';
no warnings "experimental::signatures";
use v5.36.0;
とできます(使用している処理系が、v5.30.0以降の場合に限ります)。

プロトタイプ

編集

ラムダ抽象

編集

sort のように、コードブロックを引数とするサブルーチンを考えてみましょう。

use v5.30.0;

sub bis(&) {
    my $cbr = shift;
    $cbr->();
    $cbr->()
}
bis { say 'Hello, world!' };

my $i = 0;
bis { $i++ };
say $i;
実行結果
Hello, world!
Hello, world! 
2
与えられたコードブロックを2回実行するサブルーチンです。
3行目のsub bis(&)&はラムダ抽象です。

map を模倣

編集

組込み関数 map を模倣したサブルーチン mapx を実装します。

use v5.30.0;

sub map(&@) {
    my ( $cbr, @ary ) = @_;
    my @result;
    push @result, $cbr->( local $a = $_ ) foreach @ary;
    return @result;
}
say main::map { 2 * $_ } ( 1, 2, 3 );
say main::map { 2 * $a } ( 1, 2, 3 );
say CORE::map { 2 * $_ } ( 1, 2, 3 );
say main::map { $_ x 2 } qw(a b c);
say main::map { $a x 2 } qw(a b c);
say CORE::map { $_ x 2 } qw(a b c);
実行結果
246
246
246
aabbcc
aabbcc 
aabbcc
組込み関数 sort の様に、$a でコードブロックに引数を渡すこともできるようにしました。
local で宣言しているので、スコープは foreach 式の中だけで、抜けるとグローバルな $a は埋戻されます。

reduce

編集

組込み関数に reduce がなかったので実装しました。

use v5.30.0;
use warnings;

sub reduce(&@) {
    my ( $cbr, @ary ) = @_;
    my $init = shift @ary;
    $init = $cbr->( local $a = $init, local $b = $_ ) foreach @ary;
    return $init;
}

say reduce { $_[0] + $_[1] } 1 .. 10;
say reduce { $_[0] . $_[1] } "A" .. "Z";
say reduce { $a + $b } 1 .. 10;
say reduce { $a . $b } "A" .. "Z";
実行結果
55 
ABCDEFGHIJKLMNOPQRSTUVWXYZ
55 
ABCDEFGHIJKLMNOPQRSTUVWXYZ
組込み関数 sort の様に、$a$b でコードブロックに引数を渡すこともできるようにしました。

filter

編集

組込み関数に grep は、多くの言語で filter の名前で知られる関数です。

use v5.30.0;

sub filter(&@) {
    my ( $cbr, @ary ) = @_;
    my @result = ();
    $cbr->( local $a = $_ ) ? push( @result, $_ ) : 0 foreach @ary;
    return @result;
}
say filter { $_ % 2 == 1; } 1 .. 10;
say filter { $a % 2 == 1; } 1 .. 10;
say grep { $_ % 2 == 1; } 1 .. 10;
say filter { index( "Hello world", $_ ) >= 0 } ( "A" .. "Z", "a" .. "z" );
say filter { index( "Hello world", $a ) >= 0 } ( "A" .. "Z", "a" .. "z" );
say grep { index( "Hello world", $_ ) >= 0 } ( "A" .. "Z", "a" .. "z" );
say "@{[ map { $_ * 2 } filter { $_ % 2 == 1; } 1..10]}";
say "@{[ map { $_ * 2 } filter { $a % 2 == 1; } 1..10]}";
say "@{[ map { $_ * 2 } grep { $_ % 2 == 1; } 1..10]}";
実行結果
13579
13579
13579
Hdelorw
Hdelorw
Hdelorw
2 6 10 14 18
2 6 10 14 18 
2 6 10 14 18
組込み関数 sort の様に、$a でコードブロックに引数を渡すこともできるようにしました。

コードリファレンス

編集

コードリファレンスとは?

編集

Perlにおいて「コードリファレンス」とは、サブルーチンへの参照(ポインタ)を指します。通常、サブルーチンはその名前を使って呼び出しますが、名前ではなくリファレンス(参照)として扱うことで、動的にサブルーチンを渡したり、配列やハッシュに格納して柔軟に操作することができます。

コードリファレンスの生成

編集

サブルーチンのリファレンスを生成するには、\&を使います。以下はその例です。

sub greet {
    return "Hello, World!\n";
}

my $greet_ref = \&greet;  # サブルーチンのリファレンスを取得

ここで、$greet_refgreetというサブルーチンへの参照を保持するスカラ変数になります。

コードリファレンスの呼び出し

編集

コードリファレンスを呼び出すには、->演算子を使用します。または、&を使うことでも呼び出せます。

# 例: コードリファレンスを使用してサブルーチンを呼び出す
print $greet_ref->();  # Hello, World!

# & を使っても同様
print &$greet_ref();  # Hello, World!

コードリファレンスを使う利点

編集
  1. 柔軟性: サブルーチンを動的に渡せるため、特定の条件に応じて異なるサブルーチンを実行するようなコードが書けます。
  2. データ構造との組み合わせ: 配列やハッシュにサブルーチンのリファレンスを格納することで、複数のサブルーチンを一括管理できます。
sub greet { return "Hello, World!\n"; }
sub farewell { return "Goodbye, World!\n"; }

# ハッシュでサブルーチンリファレンスを管理
my %actions = (
    greet => \&greet,
    farewell => \&farewell,
);

# 動的にサブルーチンを選んで実行
print $actions{greet}->();    # Hello, World!
print $actions{farewell}->(); # Goodbye, World!

無名サブルーチン

編集

コードリファレンスは、無名サブルーチン(名前を持たないサブルーチン)を生成する際にも使用されます。無名サブルーチンはsubキーワードだけで定義され、直接リファレンスとして利用できます。

my $say_hello = sub { return "Hello from anonymous sub!\n"; };

# コードリファレンスとして呼び出す
print $say_hello->();  # Hello from anonymous sub!

コードリファレンスの利用例

編集
サブルーチンの引数として渡す
編集

コードリファレンスを使うことで、サブルーチンを引数として別のサブルーチンに渡すことができます。

sub execute_function {
    my ($func_ref) = @_;
    print $func_ref->("Executing function!\n");
}

# 無名サブルーチンを渡す
execute_function(sub { print @_ });
イベント処理
編集

イベント駆動型プログラミングやコールバック処理で、特定のタイミングで実行するサブルーチンを指定する場合に役立ちます。

sub on_event {
    my ($callback) = @_;
    print "Event occurred!\n";
    $callback->();
}

on_event(sub { print "Callback executed!\n" });
ディスパッチテーブル
編集

関数の実行を名前で動的に選択する「ディスパッチテーブル」を構築する際に利用されます。

sub add { return $_[0] + $_[1]; }
sub subtract { return $_[0] - $_[1]; }

my %dispatch = (
    add => \&add,
    subtract => \&subtract,
);

my $operation = 'add';
print $dispatch{$operation}->(10, 5);  # 15

まとめ

編集

コードリファレンスは、サブルーチンをリファレンスとして動的に扱える仕組みです。これにより、柔軟で再利用性の高いコードを記述できるようになります。特に、動的な処理やコールバックを必要とする場面では非常に有用です。また、無名サブルーチンを利用することで、簡潔で用途に応じたコードが書けるようになります。


[TODO:スコープルールに関する簡素な説明]

[TODO:コンテキストに関する例をふんだんに使った解説]

永続的スコープのレキシカル変数

編集

my で宣言した変数(レキシカル変数)はサブルーチンを抜けると御破算になりますが、state で宣言した変数はレキシカルスコープであるものの次にサブルーチンが呼ばれたときも値を憶えています。

state $var
#!/usr/bin/perl
use v5.10;

sub func {
    my $myVar = 0;
    state $stateVar = 0;
    $myVar++;
    $stateVar++;
    CORE::say "\$myVar = $myVar, \$stateVar = $stateVar";    
}

&func for 1..5
実行結果
$myVar = 1, $stateVar = 1
$myVar = 1, $stateVar = 2
$myVar = 1, $stateVar = 3
$myVar = 1, $stateVar = 4
$myVar = 1, $stateVar = 5
state $var は、Perl 5.10 以降でサポートされています。

コンテキストとwantarray関数

編集

同じサブルーチンを呼出しても、スカラーが戻ることを期待している文脈(スカラーコンテキスト)と、リストが戻ることを期待している文脈(リストコンテキスト)の2通りがあります。 この2つのケースを判別するために wantarray 関数が用意されています。

コンテキストとwantarray関数
#!/usr/bin/perl
use strict;
use warnings;

sub func {
    return map { $_ * 2 } @_ if wantarray();
    my $sum = 0;
    $sum += $_ for @_;
    return $sum;
}

my @x = func(0, 1, 2);
my $y = func(0, 1, 2);
print <<EOS;
my \@x = func(0, 1, 2); \@x -> @x
my \$y = func(0, 1, 2); \$y -> $y
@{[ func(1,2,3) ]}
@{[ scalar func(1,2,3)]}
@{[ ~~ func(1,2,3)]}
EOS
実行結果
my @x = func(0, 1, 2); @x -> 0 2 4
my $y = func(0, 1, 2); $y -> 3
2 4 6
6 
6
wantarray 関数は、サブルーチンを呼出したコンテキストで戻値としてリストが要求されているなら真を、スカラーが要求されているなら偽を返します。
関数 func は、リストコンテキストでは全ての要素を二倍にしたリストを、スカラーコンテキストでは全ての要素の合計を返します。

組込み関数の一覧

編集

文字列:String

編集

chomp chop chr crypt fc hex index lc lcfirst length oct ord pack q/STRING/ qq/STRING/ reverse rindex sprintf substr tr/// uc ucfirst y///

書式
index STR, SUBSTR [, POSITION]</syntaxhighlight copy>
;機能:<syntaxhighlight lang=text>文字列STRの中で部分文字列SUBSTRが最初に出現する位置を返します
組込み関数 index
#!/usr/bin/env perl
use v5.30.0;
use warnings;

my $str = "This is a pen.";
my $substr = "pen";
say qq(index "$str", "$substr" -> @{[index $str, $substr]});

$str = "これは、ペンです。";
$substr = "ペン";
say qq(index "$str", "$substr" -> @{[index $str, $substr]});

use Encode qw(encode decode);

$str = decode('utf-8', "これは、ペンです。");
$substr = decode('utf-8', "ペン");
say encode('utf-8', qq(index "$str", "$substr" -> @{[index $str, $substr]}));
実行結果
index "This is a pen.", "pen" -> 10 
index "これは、ペンです。", "ペン" -> 12  
index "これは、ペンです。", "ペン" -> 4
pen の前にある This is a は空白も含めて合計で10文字なので、10 が表示されます。
文字列とインデックスの対応
0 1 2 3 4 5 6 7 8 9 10 11 12 13
T h i s   i s   a   p e n .
解説
indexは、文字列 STR の中から、検索文字列 SUBSTR を探し、最初に見つかった位置を返します。検索文字が見つからない場合には、-1 が返ります。
省略可能な引数、POSITION には、検索開始位置を指定します(ディフォルトは0)。
POSITION を使うと部分文字列が2回め以降に出現する位置も確かめることが出来、部分文字列の長さに注意すれば部分文字列の出現回数を数えることなどが容易になります。
位置が 0 から始まることに留意しましょう。 0 は文字列の左端を表します。
位置は文字単位ではなくバイト数なので、ソースコードエンコーディングが UTF-8 で多バイト文字が交じると、文字数とバイト数に食い違いが生じます。
このような場合は Encode モジュールを使い内部形式( internal format )に変換します。
内部形式であれば、サロゲートペアにも対応できますが、合成文字は修飾コードと基底文字はそれぞれ1文字に数えられます。
utf8プラグマを使う
編集
別解(utf8プラグマを使う)
#!/usr/bin/perl
use v5.30.0;
use warnings;
use utf8;

my $str = "This is a pen.";
my $substr = "pen";
say qq(index "$str", "$substr" -> @{[index $str, $substr]});

use Encode qw(encode);

$str = "これは、ペンです。";
$substr = "ペン";
say encode('utf-8', qq(index "$str", "$substr" -> @{[index $str, $substr]}));
実行結果
index "This is a pen.", "pen" -> 10  
index "これは、ペンです。", "ペン" -> 4
utf8プラグマを使い、ソースコードエンコーディングが UTF-8 であることを明示すると、文字リテラルは内部形式に変換され index や length で処理されます。
この場合でも、出力するときに内部形式から UTF-8 にエンコードする必要があります。
binmodeを使う
編集
別解(binmodeを使う)
#!/usr/bin/perl
use v5.30.0;
use warnings;
use utf8;
binmode STDOUT,":encoding(UTF-8)";

my $str = "This is a pen.";
my $substr = "pen";
say qq(index "$str", "$substr" -> @{[index $str, $substr]});

$str = "これは、ペンです。";
$substr = "ペン";
say qq(index "$str", "$substr" -> @{[index $str, $substr]});
実行結果
index "This is a pen.", "pen" -> 10  
index "これは、ペンです。", "ペン" -> 4
毎回エンコードせず、STDOUT のディフォルトエンコーディングを UTF-8 にかえました。
binmode STDIN,":encoding(UTF-8)";binmode STDERR,":encoding(UTF-8)";も同時に指定したほうがいいかもしれません。

テキストのエンコーディングは、Perlを使っていると度々トラブルのもとになるので、回避方法が幾つかある事を知っておくと、他人の書いたコードを読むときなどに役に立ちます。 ここで紹介した方法の他に、歌代さんのjcode.plなどもあるのですが、標準モジュールの範囲の説明に留めました。

rindex

編集
書式
rindex (STR, SUBSTR, [POSITION])</syntaxhighlight copy>
;機能:文字列STRの中で部分文字列SUBSTRが'''最後'''に出現する位置を返します
;[https://paiza.io/projects/BcE2R3K_lHUDlhNTQWwY3Q?language=perl 組込み関数 rindex]:<syntaxhighlight lang=perl copy>
#!/usr/bin/perl
use v5.30.0;
use warnings;
use utf8;
binmode STDOUT,":encoding(UTF-8)";

my $str = "I like pens and pencils.";
my $substr = "pen";
say qq(rindex "$str", "$substr" -> @{[rindex $str, $substr]});

$str = "私は筆と鉛筆が好きです。";
$substr = "筆";
say qq(rindex "$str", "$substr" -> @{[rindex $str, $substr]});
実行結果
rindex "I like pens and pencils.", "pen" -> 16  
rindex "私は筆と鉛筆が好きです。", "筆" -> 5
解説
rindexは、文字列 STR の中から、検索文字列 SUBSTR を探し、最後に見つかった位置を返します(「末尾からの位置を返す」との編集が過去にありましたが、間違いです)。検索文字が見つからない場合には、-1 が返ります。
省略可能な引数、POSITION には、検索開始位置を指定します(ディフォルトは0)。

substr

編集
書式
substr (EXPR, OFFSET, [LENGTH], [REPLACEMENT])</syntaxhighlight copy>

文字列 EXPR からOFFSET 目以降のバイト列を返します取り出す長さ LENGTH をバイト単位で指定できますが省略した場合は文字列の最後まで取り出しますなおutf8プラグマが有効な場合はバイト単位ではなく文字単位で取り出すことができます

位置情報 OFFSET は上述のとおり 0 から始まりますがLENGTH は容量なので通常は 1 以上の値を指定します

文字列 REPLACEMENT を指定すると取り出される部分を REPLACEMENT で置換します

;[https://paiza.io/projects/BcE2R3K_lHUDlhNTQWwY3Q?language=perl 組込み関数 rindex]:<syntaxhighlight lang=perl copy>
#!/usr/bin/perl
use v5.30.0;
use warnings;
use utf8;
binmode STDOUT,":encoding(UTF-8)";

my $str = "Hello, world!";
say substr($str, index($str, "world"), length("world"), "Japan");
say $str;

$str = "こんにちは、世界!";
say substr($str, index($str, "世界"), length("世界"), "🌍");
say $str;
実行結果
world
Hello, Japan!
世界 
こんにちは、🌍!
書式
uc ([EXPR])</syntaxhighlight copy>
:文字列 EXPR を大文字にして返しますEXPR を省略すると$_ が使われます

==== ucfirst ====
;書式:<syntaxhighlight lang=perl>ucfirst ([EXPR])</syntaxhighlight copy>
:uc と同じですが先頭1文字を大文字にして返します

==== lc ====
;書式:<syntaxhighlight lang=perl>lc ([EXPR])</syntaxhighlight copy>
:uc と同じですが小文字にして返します

==== lcfirst ====
;書式:<syntaxhighlight lang=perl>lcfirst ([EXPR])</syntaxhighlight copy>
:ucfirst と同じですが小文字にして返します

==== chop ====
;書式:<syntaxhighlight lang=perl copy>
chop VARIABLE
chop (LIST)
変数 VARIABLE の末尾の末尾1文字を削除します。
変数のリストを渡された場合は、各変数について同じ処理を行います。
VARIABLE を省略すると $_ が使われます。
chopとchomp
#!/usr/bin/perl
use v5.30.0;
use warnings;
use utf8;
binmode STDOUT,":encoding(UTF-8)";

my $str = "Hello, world!\n";
chop $str;
say "chop: $str(@{[length $str]})";
chop $str;
say "chop: $str(@{[length $str]})";
chop $str;
say "chop: $str(@{[length $str]})";
chop $str;
say "chop: $str(@{[length $str]})";

$str = "Hello, world!\n";
chomp $str;
say "chomp: $str(@{[length $str]})";
chomp $str;
say "chomp: $str(@{[length $str]})";
chomp $str;
say "chomp: $str(@{[length $str]})";
chomp $str;
say "chomp: $str(@{[length $str]})";

$str = "Hello, world!\n";
$str = substr($str, 0, length($str) - 1);
say "substr(): $str(@{[length $str]})";
$str = substr($str, 0, length($str) - 1);
say "substr(): $str(@{[length $str]})";
$str = substr($str, 0, length($str) - 1);
say "substr(): $str(@{[length $str]})";

sub chop(\$) {
    my $strr = shift;
    $$strr = substr($$strr, 0, length($$strr) - 1);
    undef
}

$str = "Hello, world!\n";
::chop $str;
say "::chop: $str(@{[length $str]})";
::chop $str;
say "::chop: $str(@{[length $str]})";
::chop $str;
say "::chop: $str(@{[length $str]})";

sub chomp(\$) {
    my $strr = shift;
    $$strr = substr($$strr, 0, length($$strr) - 1) if substr($$strr, length($$strr) - 1, 1) eq "\n";
    undef
}

$str = "Hello, world!\n";
::chomp $str;
say "::chomp: $str(@{[length $str]})";
::chomp $str;
say "::chomp: $str(@{[length $str]})";
実行結果
chop: Hello, world!(13)
chop: Hello, world(12)
chop: Hello, worl(11)
chop: Hello, wor(10)
chomp: Hello, world!(13)
chomp: Hello, world!(13)
chomp: Hello, world!(13)
chomp: Hello, world!(13)
substr(): Hello, world!(13)
substr(): Hello, world(12)
substr(): Hello, worl(11)
::chop: Hello, world!(13)
::chop: Hello, world(12)
::chop: Hello, worl(11)
::chomp: Hello, world!(13) 
::chomp: Hello, world!(13)
chop は、文字列を末尾から喰います(破壊的)
chomp は、文字列の末尾の改行を喰います(破壊的)
書式
chomp VARIABLE
chomp (LIST)
変数 VARIABLE の末尾の $/(デフォルトは "\n")を削除します。
変数のリストを渡された場合は、各変数について同じ処理を行います。
VARIABLE を省略すると $_ が使われます。
書式
chr [NUMBER]</syntaxhighlight copy>
: 文字セットで NUMBER 番目に割り当てられている文字を返します
: NUMBER を省略すると $_ が使われます
: 逆の操作を行うには ord を使います

==== crypt ====
;書式:<syntaxhighlight lang=perl>crypt PLAINTEXT, SALT</syntaxhighlight copy>
:C ライブラリの crypt(3) をエミュレートします

==== hex ====
;書式:<syntaxhighlight lang=perl>hex [EXPR]</syntaxhighlight copy>
: 十六進数 EXPR を十進数に変換して返しますEXPR を省略すると $_ が使われます

==== length ====
;書式:<syntaxhighlight lang=perl>length [EXPR]</syntaxhighlight copy>
: 文字列 EXPR の長さを返しますbytes プラグマが有効な場合デフォルトはバイト数をutf8 プラグマが有効な場合は文字数を返しますEXPR を省略すると $_ が使われます

==== oct ====
;書式:<syntaxhighlight lang=perl>oct [EXPR]</syntaxhighlight copy>
: 八進数 EXPR を十進数に変換して返しますEXPR を省略すると $_ が使われます

==== ord ====
;書式:<syntaxhighlight lang=perl>ord [EXPR]</syntaxhighlight copy>
: 文字列 EXPR の文字セット上でのコード位置を返しますEXPR を省略すると $_ が使われます逆の操作を行うには chr を使います

==== pack ====
<code>pack</code>関数は指定されたフォーマットに従ってデータをバイナリ文字列にパックします主にバイナリデータを処理する際に使用されます以下は<code>pack</code>関数の基本的な使用例と一般的なフォーマット指定子の一部です

:<syntaxhighlight lang=perl copy>
# バイナリ文字列を作成する例
my $binary_data = pack('C4 A4', 65, 66, 67, 68, 'Hello');

# 上記の例では、'C4'は4つのバイトの符号なし整数を、'A4'は4バイトの文字列を表します。
# したがって、65, 66, 67, 68はバイナリデータにパックされ、それに続く'Hello'もパックされます。

# バイナリ文字列を出力する
print "$binary_data\n";  # 出力例: ABCDHello

# バイナリ文字列からデータをアンパックする例
my ($num1, $num2, $string) = unpack('C4 A4', $binary_data);

print "$num1 $num2 $string\n";  # 出力例: 65 66 Hello

一般的なフォーマット指定子の一部は以下の通りです。

  • A - 文字列
  • C - 符号なし8ビット整数 (1 バイト)
  • S - 符号なし16ビット整数 (2 バイト)
  • L - 符号なし32ビット整数 (4 バイト)
  • n - ネットワークバイトオーダー (big-endian) の16ビット整数
  • N - ネットワークバイトオーダーの32ビット整数
  • v - リトルエンディアンバイトオーダーの16ビット整数
  • V - リトルエンディアンバイトオーダーの32ビット整数

pack関数とunpack関数は、バイナリデータを他の形式に変換する際に非常に便利です。

q/STRING/
qq/STRING/
qr/STRING/
qx/STRING/
qw/STRING/

シングルクォート、ダブルクォート、正規表現、バッククォート、単語クォート。詳細は演算子の章を参照。

reverse

編集
書式
reverse LIST</syntaxhighlight copy>
:リストコンテキストでは LIST の順番を逆順にしたリストを返しますスカラーコンテキストでは LIST の要素を結合した後に逆順にした文字列を返します
;[https://paiza.io/projects/m274vBmTsDDTfyB1yKQy3g?language=perl ]:<syntaxhighlight lang=perl copy>
use v5.30.0;
use warnings;

my @array = qw(あい うえお かきくけこ 🏝);
say "@{[ reverse @array ]}";
say "@{[ scalar reverse @array ]}";

use utf8;
binmode STDOUT,":encoding(UTF-8)";
@array = qw(あい うえお かきくけこ 🏝);
say "@{[ reverse @array ]}";
say "@{[ scalar reverse @array ]}";
実行結果
🏝 かきくけこ うえお あい
����㏁㍁㋁
🏝 かきくけこ うえお あい 
🏝こけくきかおえういあ
Perlの文字列はディフォルトではバイトシーケンスなのでバイト逆順にすると多バイト文字は破綻し、上記のように文字化けします。
use utf8;で、バイトシーケンスから内部エンコーディング( Wide character )に切替えることができますが、このまま say すると内部エンコーディングのままなので、標準出力のレイヤーを ":encoding(UTF-8)" に変更します。

sprintf

編集
書式
sprintf FORMAT, LIST</syntaxhighlight copy>
:LIST  FORMAT に従って整形して返します
==== tr ====
;書式:<syntaxhighlight lang=perl>tr///</syntaxhighlight copy>
:1文字を対応する1文字に置換します詳細は[[Perl/演算子|演算子]]の章を参照
==== y ====
;書式:<syntaxhighlight lang=perl>y///</syntaxhighlight copy>
:tr///と同義

=== 正規表現とパターンマッチ ===

Perlにはテキスト処理やパターンマッチングに役立つ様々な機能が備わっています
以下ではそれぞれの機能について解説します

; m// 演算子
: パターンマッチング演算子として知られており文字列内でパターンを検索するために使用されます正規表現を使ってパターンを指定しマッチした部分を取得したりマッチングの成否を確認したりすることができます例えば次のように使用します
:<syntaxhighlight lang=perl copy>
my $str = "Hello World";
if ($str =~ m/World/) {
    print "マッチしました\n";
}
pos 関数
pos 関数は、直近のパターンマッチングでマッチした部分文字列の次の検索位置を示します。次のパターンマッチングでの開始位置を変更するために使用されます。例えば、次のように使用します。
my $str = "abcabc";
while ($str =~ m/abc/g) {
    print "マッチ位置: " . pos($str) . "\n";
}
qr// 演算子
qr// 演算子は、正規表現を表すオブジェクトを生成します。これは、後で同じ正規表現を再利用する場合に便利です。例えば、次のように使用します。
my $pattern = qr/abc/;
if ($str =~ $pattern) {
    print "マッチしました\n";
}
quotemeta 関数
quotemeta 関数は、文字列内のメタ文字をエスケープします。これにより、文字列をそのままパターンとして使用する際に、意図しない動作を防ぐことができます。例えば、次のように使用します。
my $str = "a+";
my $pattern = quotemeta($str);
if ($input =~ m/$pattern/) {
    print "マッチしました\n";
}
s/// 演算子
置換演算子として知られており、文字列内のパターンにマッチする部分を置換します。例えば、次のように使用します。
my $str = "apple orange";
$str =~ s/apple/banana/;
print $str;  # "banana orange" を出力
split 関数
split 関数は、文字列を指定した区切り文字で分割し、リストに格納します。例えば、次のように使用します。
my $str = "apple,orange,banana";
my @fruits = split /,/, $str;
print join("-", @fruits);  # "apple-orange-banana" を出力
study 関数
study 関数は、文字列内のパターンマッチングを高速化するための最適化を行います。特に大きな文字列を検索する場合に有効です。例えば、次のように使用します。
my $str = "very long string...";
study $str;
if ($str =~ m/pattern/) {
    print "マッチしました\n";
}

これらの機能を使用することで、Perlでのテキスト処理やパターンマッチングを効率的に行うことができます。

数値演算関数

編集
abs, atan2, cos, exp, hex, int, log, oct, rand, sin, sqrt, srand

これらの関数はPerlで数学的な操作を行うための組み込み関数です。以下にそれぞれの関数を簡単に説明します。

abs
数値の絶対値を返します。
my $num = -10;
my $abs_num = abs($num);  # $abs_num には 10 が格納されます
atan2
y/x のアークタンジェントを返します。y と x を引数に取ります。
my $atan_value = atan2($y, $x);
cos
弧度法で与えられた角度の余弦を返します。
my $cos_value = cos($angle);
exp
指数関数   を返します。
my $exp_value = exp($x);
hex
文字列を 16 進数として解釈し、整数値を返します。
my $hex_value = hex("0xFF");  # $hex_value には 255 が格納されます
int
数値の整数部分を返します。
my $num = 3.6;
my $int_num = int($num);  # $int_num には 3 が格納されます
log
自然対数を返します。
my $log_value = log($x);
oct
文字列を 8 進数として解釈し、整数値を返します。
my $oct_value = oct("077");  # $oct_value には 63 が格納されます
rand
0 から 1 未満の乱数を返します。
my $rand_value = rand();
sin
弧度法で与えられた角度の正弦を返します。
my $sin_value = sin($angle);
sqrt
数値の平方根を返します。
my $sqrt_value = sqrt($num);
srand
乱数のシードを設定します。これにより、乱数生成のシーケンスが初期化されます。
srand(time);  # 現在時刻をシードとして使用する例

これらの関数を使用することで、Perlでの数学的な操作を簡単に行うことができます。

配列操作

編集
each, keys, pop, push, shift, splice, unshift, values

これらの関数は、Perlで配列を操作するための組み込み関数です。以下にそれぞれの関数を説明します。

each
配列を反復処理するために使用されます。各反復で、配列内の次の要素を返します。
my @array = ('a', 'b', 'c');
while (my ($index, $value) = each @array) {
    print "Index: $index, Value: $value\n";
}
keys
配列のインデックス(キー)を取得します。通常、数値のインデックスが返されます。
my @array = ('a', 'b', 'c');
my @indices = keys @array;  # @indices には (0, 1, 2) が格納されます
pop
配列の末尾から要素を取り出します(削除)。
my @array = (1, 2, 3, 4, 5);
my $last_element = pop @array;  # $last_element には 5 が格納され、@array は (1, 2, 3, 4) になります
push
配列の末尾に要素を追加します。
my @array = (1, 2, 3);
push @array, 4, 5;  # @array は (1, 2, 3, 4, 5) になります
shift
配列の先頭から要素を取り出します(削除)。
my @array = (1, 2, 3, 4, 5);
my $first_element = shift @array;  # $first_element には 1 が格納され、@array は (2, 3, 4, 5) になります
splice
配列の指定した範囲の要素を置換または削除し、新しい要素を挿入します。
my @array = (1, 2, 3, 4, 5);
splice @array, 2, 2, 'a', 'b';  # @array は (1, 2, 'a', 'b', 5) になります
unshift
配列の先頭に要素を追加します。
my @array = (3, 4, 5);
unshift @array, 1, 2;  # @array は (1, 2, 3, 4, 5) になります

これらの関数を使用することで、Perlで配列を効果的に操作することができます。

リスト操作

編集
grep, join, map, qw//, reverse, sort, unpack

Perlにおけるリスト操作に関する関数や演算子について解説します。

grep
リスト内の要素をフィルタリングします。条件にマッチする要素だけを残した新しいリストを返します。
my @numbers = (1, 2, 3, 4, 5);
my @even_numbers = grep { $_ % 2 == 0 } @numbers;  # @even_numbers には (2, 4) が格納されます
join
リストの要素を指定した区切り文字で結合して文字列を生成します。
my @words = ('hello', 'world', '!');
my $sentence = join ' ', @words;  # $sentence には "hello world !" が格納されます
map
リストの各要素に対して処理を行い、結果を新しいリストとして返します。
my @numbers = (1, 2, 3);
my @squared_numbers = map { $_ ** 2 } @numbers;  # @squared_numbers には (1, 4, 9) が格納されます
qw//
クォートワード演算子。文字列をスペースで区切り、リストとして返します。
my @words = qw(apple banana orange);  # @words には ('apple', 'banana', 'orange') が格納されます
reverse
リストの要素の順序を逆にします。
my @numbers = (1, 2, 3);
my @reversed_numbers = reverse @numbers;  # @reversed_numbers には (3, 2, 1) が格納されます
sort
リストの要素をソートします。デフォルトでは文字列としてソートされますが、数値としてソートしたい場合は sort {$a <=> $b} のようにコードブロックを指定します。
my @numbers = (3, 1, 2);
my @sorted_numbers = sort @numbers;  # @sorted_numbers には (1, 2, 3) が格納されます
unpack
文字列を指定したテンプレートに従って解釈し、リストとして返します。主にバイナリデータの処理に使用されます。
my $binary_data = "\x41\x42\x43";
my @values = unpack 'C*', $binary_data;  # @values には (65, 66, 67) が格納されます

これらの関数や演算子を使うことで、Perlでリストを効果的に操作することができます。

ハッシュ操作

編集
delete, each, exists, keys, values

Perlにおけるハッシュ(連想配列)の操作について説明します。

delete
ハッシュから指定したキーとその関連する値を削除します。
my %hash = ('a' => 1, 'b' => 2, 'c' => 3);
delete $hash{'b'};  # ハッシュから 'b' キーとその値を削除します
each
ハッシュを反復処理するために使用されます。各反復で、キーと値のペアを返します。
my %hash = ('a' => 1, 'b' => 2, 'c' => 3);
while (my ($key, $value) = each %hash) {
    print "$key: $value\n";
}
exists
指定したキーがハッシュ内に存在するかどうかを確認します。存在する場合は真を返し、存在しない場合は偽を返します。
my %hash = ('a' => 1, 'b' => 2, 'c' => 3);
if (exists $hash{'b'}) {
    print "'b' キーは存在します\n";
} else {
    print "'b' キーは存在しません\n";
}
keys
ハッシュのキーのリストを取得します。
my %hash = ('a' => 1, 'b' => 2, 'c' => 3);
my @keys = keys %hash;  # @keys には ('a', 'b', 'c') が格納されます
values
ハッシュの値のリストを取得します。
my %hash = ('a' => 1, 'b' => 2, 'c' => 3);
my @values = values %hash;  # @values には (1, 2, 3) が格納されます

これらの関数や演算子を使うことで、Perlでハッシュを効果的に操作することができます。

binmode, close, closedir, dbmclose, dbmopen, die, eof, fileno, flock, format, getc, print, printf, read, readdir, readline, rewinddir, say, seek, seekdir, select, syscall, sysread, sysseek, syswrite, tell, telldir, truncate, warn, write


固定長データとレコード

編集
pack, read, syscall, sysread, sysseek, syswrite, unpack, vec

ファイルハンドル・ファイルとディレクトリ

編集
-X, chdir, chmod, chown, chroot, fcntl, glob, ioctl, link, lstat, mkdir, open, opendir, readlink, rename, rmdir, select, stat, symlink, sysopen, umask, unlink, utime

制御構造

編集
break, caller, continue, die, do, dump, eval, evalbytes, exit, __FILE__, goto, last, __LINE__, next, __PACKAGE__, redo, return, sub, __SUB__, wantarray

スコープ

編集
caller, import, local, my, our, package, state, use
defined, formline, lock, prototype, reset, scalar, undef

プロセス

編集
alarm, exec, fork, getpgrp, getppid, getpriority, kill, pipe, qx//, readpipe, setpgrp, setpriority, sleep, system, times, wait, waitpid

モジュール

編集
do, import, no, package, require, use

オブジェクト指向

編集
bless, dbmclose, dbmopen, package, ref, tie, tied, untie, use

Socket

編集
accept, bind, connect, getpeername, getsockname, getsockopt, listen, recv, send, setsockopt, shutdown, socket, socketpair

System V IPC

編集
msgctl, msgget, msgrcv, msgsnd, semctl, semget, semop, shmctl, shmget, shmread, shmwrite

ユーザーとグループ

編集
endgrent, endhostent, endnetent, endpwent, getgrent, getgrgid, getgrnam, getlogin, getpwent, getpwnam, getpwuid, setgrent, setpwent

ネットワーク情報

編集
endprotoent, endservent, gethostbyaddr, gethostbyname, gethostent, getnetbyaddr, getnetbyname, getnetent, getprotobyname, getprotobynumber, getprotoent, getservbyname, getservbyport, getservent, sethostent, setnetent, setprotoent, setservent

日付時刻

編集
gmtime, localtime, time, times

関数以外のキーワード

編集
and, AUTOLOAD, BEGIN, catch, CHECK, cmp, CORE, __DATA__, default, defer, DESTROY, else, elseif, elsif, END, __END__, eq, finally, for, foreach, ge, given, gt, if, INIT, isa, le, lt, ne, not, or, try, UNITCHECK, unless, until, when, while, x, xor


組込み関数一覧

編集

each keys pop push shift splice unshift values

retrieve the next key/value pair from a hash

retrieve list of indices from a hash

remove the last element from an array and return it

append one or more elements to an array

remove the first element of an array, and return it

splice

編集

add or remove elements anywhere in an array

unshift

編集

prepend more elements to the beginning of a list

values

編集

return a list of the values in a hash

Binary

編集

pack read syscall sysread sysseek syswrite unpack vec

convert a list into a binary representation

fixed-length buffered input from a filehandle

syscall

編集

execute an arbitrary system call

sysread

編集

fixed-length unbuffered input from a filehandle

sysseek

編集

position I/O pointer on handle used with sysread and syswrite

syswrite

編集

fixed-length unbuffered output to a filehandle

unpack

編集

Perlのunpack関数は、指定されたフォーマットに従ってバイナリ文字列をアンパックしてデータを取得します。主にバイナリデータを処理する際に使用されます。以下は、unpack関数の基本的な使用例と一般的なフォーマット指定子の一部です。

# バイナリ文字列を作成する例
my $binary_data = "ABCDHello";

# バイナリ文字列をアンパックしてデータを取得する
my ($num1, $num2, $string) = unpack('C4 A4', $binary_data);

print "$num1 $num2 $string\n";  #=> 65 66 67

一般的なフォーマット指定子の一部は以下の通りです。

  • A - 文字列
  • C - 符号なし8ビット整数 (1 バイト)
  • S - 符号なし16ビット整数 (2 バイト)
  • L - 符号なし32ビット整数 (4 バイト)
  • n - ネットワークバイトオーダー (big-endian) の16ビット整数
  • N - ネットワークバイトオーダーの32ビット整数
  • v - リトルエンディアンバイトオーダーの16ビット整数
  • V - リトルエンディアンバイトオーダーの32ビット整数

unpack関数は、バイナリデータを他の形式に変換する際に非常に便利です。

test or set particular bits in a string

-X chdir chmod chown chroot fcntl glob ioctl link lstat mkdir open opendir readlink rename rmdir select stat symlink sysopen umask unlink utime

a file test (-r, -x, etc)

change your current working directory

changes the permissions on a list of files

change the ownership on a list of files

chroot

編集

make directory new root for path lookups

file control system call

expand filenames using wildcards

system-dependent device control system call

create a hard link in the filesystem

stat a symbolic link

create a directory

open a file, pipe, or descriptor

opendir

編集

open a directory

編集

determine where a symbolic link is pointing

rename

編集

change a filename

remove a directory

select

編集

reset default output or do I/O multiplexing

get a file's status information

編集

create a symbolic link to a file

sysopen

編集

open a file, pipe, or descriptor

set file creation mode mask

編集

remove one link to a file

set a file's last access and modify times

break caller continue die do dump eval evalbytes exit __FILE__ goto last __LINE__ next __PACKAGE__ redo return sub __SUB__ wantarray

break out of a C<given> block

caller

編集

get context of the current subroutine call

continue

編集

optional trailing block in a while or foreach

raise an exception or bail out

turn a BLOCK into a TERM

create an immediate core dump

catch exceptions or compile and run code

evalbytes

編集

similar to string eval, but intend to parse a bytestream

terminate this program

__FILE__

編集

the name of the current source file

create spaghetti code

exit a block prematurely

__LINE__

編集

the current source line number

iterate a block prematurely

__PACKAGE__

編集

the current package

start this loop iteration over again

return

編集

get out of a function early

declare a subroutine, possibly anonymously

__SUB__

編集

the current subroutine, or C<undef> if not in a subroutine

wantarray

編集

get void vs scalar vs list context of current subroutine call

delete each exists keys values

delete

編集

deletes a value from a hash

retrieve the next key/value pair from a hash

exists

編集

test whether a hash key is present

retrieve list of indices from a hash

values

編集

return a list of the values in a hash

binmode close closedir dbmclose dbmopen die eof fileno flock format getc print printf read readdir readline rewinddir say seek seekdir select syscall sysread sysseek syswrite tell telldir truncate warn write

binmode

編集

prepare binary files for I/O

close file (or pipe or socket) handle

closedir

編集

close directory handle

dbmclose

編集

breaks binding on a tied dbm file

dbmopen

編集

create binding on a tied dbm file

raise an exception or bail out

test a filehandle for its end

fileno

編集

return file descriptor from filehandle

lock an entire file with an advisory lock

format

編集

declare a picture format with use by the write() function

get the next character from the filehandle

output a list to a filehandle

printf

編集

output a formatted list to a filehandle

fixed-length buffered input from a filehandle

readdir

編集

get a directory from a directory handle

readline

編集

fetch a record from a file

rewinddir

編集

reset directory handle

output a list to a filehandle, appending a newline

reposition file pointer for random-access I/O

seekdir

編集

reposition directory pointer

select

編集

reset default output or do I/O multiplexing

syscall

編集

execute an arbitrary system call

sysread

編集

fixed-length unbuffered input from a filehandle

sysseek

編集

position I/O pointer on handle used with sysread and syswrite

syswrite

編集

fixed-length unbuffered output to a filehandle

get current seekpointer on a filehandle

telldir

編集

get current seekpointer on a directory handle

truncate

編集

shorten a file

print debugging info

print a picture record

grep join map qw/STRING/ reverse sort unpack

locate elements in a list test true against a given criterion

join a list into a string using a separator

apply a change to a list to get back a new list with the changes

qw/STRING/

編集

quote a list of words

reverse

編集

flip a string or a list

sort a list of values

unpack

編集

convert binary structure into normal perl variables

abs atan2 cos exp hex int log oct rand sin sqrt srand

absolute value function

arctangent of Y/X in the range -PI to PI

cosine function

raise I<e> to a power

convert a hexadecimal string to a number

get the integer portion of a number

retrieve the natural logarithm for a number

convert a string to an octal number

retrieve the next pseudorandom number

return the sine of a number

square root function

seed the random number generator

defined formline lock prototype reset scalar undef

defined

編集

test whether a value, variable, or function is defined

formline

編集

internal function used for formats

get a thread lock on a variable, subroutine, or method

prototype

編集

get the prototype (if any) of a subroutine

clear all variables of a given name

scalar

編集

force a scalar context

remove a variable or function definition

Modules

編集

do import no package require use

turn a BLOCK into a TERM

import

編集

patch a module's namespace into your own

unimport some module symbols or semantics at compile time

package

編集

declare a separate global namespace

require

編集

load in external functions from a library at runtime

load in a module at compile time and import its namespace

Namespace

編集

caller import local my our package state use

caller

編集

get context of the current subroutine call

import

編集

patch a module's namespace into your own

create a temporary value for a global variable (dynamic scoping)

declare and assign a local variable (lexical scoping)

declare and assign a package variable (lexical scoping)

package

編集

declare a separate global namespace

declare and assign a persistent lexical variable

load in a module at compile time and import its namespace

Network

編集

endprotoent endservent gethostbyaddr gethostbyname gethostent getnetbyaddr getnetbyname getnetent getprotobyname getprotobynumber getprotoent getservbyname getservbyport getservent sethostent setnetent setprotoent setservent

endprotoent

編集

be done using protocols file

endservent

編集

be done using services file

gethostbyaddr

編集

get host record given its address

gethostbyname

編集

get host record given name

gethostent

編集

get next hosts record

getnetbyaddr

編集

get network record given its address

getnetbyname

編集

get networks record given name

getnetent

編集

get next networks record

getprotobyname

編集

get protocol record given name

getprotobynumber

編集

get protocol record numeric protocol

getprotoent

編集

get next protocols record

getservbyname

編集

get services record given its name

getservbyport

編集

get services record given numeric port

getservent

編集

get next services record

sethostent

編集

prepare hosts file for use

setnetent

編集

prepare networks file for use

setprotoent

編集

prepare protocols file for use

setservent

編集

prepare services file for use

Objects

編集

bless dbmclose dbmopen package ref tie tied untie use

create an object

dbmclose

編集

breaks binding on a tied dbm file

dbmopen

編集

create binding on a tied dbm file

package

編集

declare a separate global namespace

find out the type of thing being referenced

bind a variable to an object class

get a reference to the object underlying a tied variable

break a tie binding to a variable

load in a module at compile time and import its namespace

Process

編集

alarm exec fork getpgrp getppid getpriority kill pipe qx/STRING/ readpipe setpgrp setpriority sleep system times wait waitpid

schedule a SIGALRM

abandon this program to run another

create a new process just like this one

getpgrp

編集

get process group

getppid

編集

get parent process ID

getpriority

編集

get current nice value

send a signal to a process or process group

open a pair of connected filehandles

qx/STRING/

編集

backquote quote a string

readpipe

編集

execute a system command and collect standard output

setpgrp

編集

set the process group of a process

setpriority

編集

set a process's nice value

block for some number of seconds

system

編集

run a separate program

return elapsed time for self and child processes

wait for any child process to die

waitpid

編集

wait for a particular child process to die

Regexp

編集

m// pos qr/STRING/ quotemeta s/// split study

match a string with a regular expression pattern

find or set the offset for the last/next m//g search

qr/STRING/

編集

compile pattern

quotemeta

編集

quote regular expression magic characters

replace a pattern with a string

split up a string using a regexp delimiter

no-op, formerly optimized input data for repeated searches

Socket

編集

accept bind connect getpeername getsockname getsockopt listen recv send setsockopt shutdown socket socketpair

accept

編集

accept an incoming socket connect

binds an address to a socket

connect

編集

connect to a remote socket

getpeername

編集

find the other end of a socket connection

getsockname

編集

retrieve the sockaddr for a given socket

getsockopt

編集

get socket options on a given socket

listen

編集

register your socket as a server

receive a message over a Socket

send a message over a socket

setsockopt

編集

set some socket options

shutdown

編集

close down just half of a socket connection

socket

編集

create a socket

socketpair

編集

create a pair of sockets

String

編集

chomp chop chr crypt fc hex index lc lcfirst length oct ord pack q/STRING/ qq/STRING/ reverse rindex sprintf substr tr/// uc ucfirst y///

remove a trailing record separator from a string

remove the last character from a string

get character this number represents

one-way passwd-style encryption

return casefolded version of a string

convert a hexadecimal string to a number

find a substring within a string

return lower-case version of a string

lcfirst

編集

return a string with just the next letter in lower case

length

編集

return the number of characters in a string

convert a string to an octal number

find a character's numeric representation

convert a list into a binary representation

q/STRING/

編集

singly quote a string

qq/STRING/

編集

doubly quote a string

reverse

編集

flip a string or a list

rindex

編集

right-to-left substring search

sprintf

編集

formatted print into a string

substr

編集

get or alter a portion of a string

transliterate a string

return upper-case version of a string

ucfirst

編集

return a string with just the next letter in upper case

transliterate a string

msgctl msgget msgrcv msgsnd semctl semget semop shmctl shmget shmread shmwrite

msgctl

編集

SysV IPC message control operations

msgget

編集

get SysV IPC message queue

msgrcv

編集

receive a SysV IPC message from a message queue

msgsnd

編集

send a SysV IPC message to a message queue

semctl

編集

SysV semaphore control operations

semget

編集

get set of SysV semaphores

SysV semaphore operations

shmctl

編集

SysV shared memory operations

shmget

編集

get SysV shared memory segment identifier

shmread

編集

read SysV shared memory

shmwrite

編集

write SysV shared memory

gmtime localtime time times

gmtime

編集

convert UNIX time into record or string using Greenwich time

localtime

編集

convert UNIX time into record or string using local time

return number of seconds since 1970

return elapsed time for self and child processes

endgrent endhostent endnetent endpwent getgrent getgrgid getgrnam getlogin getpwent getpwnam getpwuid setgrent setpwent

endgrent

編集

be done using group file

endhostent

編集

be done using hosts file

endnetent

編集

be done using networks file

endpwent

編集

be done using passwd file

getgrent

編集

get next group record

getgrgid

編集

get group record given group user ID

getgrnam

編集

get group record given group name

getlogin

編集

return who logged in at this tty

getpwent

編集

get next passwd record

getpwnam

編集

get passwd record given user login name

getpwuid

編集

get passwd record given user ID

setgrent

編集

prepare group file for use

setpwent

編集

prepare passwd file for use



入出力・コマンドラインオプション

編集

ファイルI/Oとコマンドラインオプションについて解説します。


外部ファイルに書き込みたい場合

編集
open(my $handle, ">" , "testperl.txt") or die;
print $handle "zzzzz";
解説
まず、外部ファイルに読み書きしたい場合、組み込み関数の open関数 で目的のファイルを開きます。
ファイルは、open することで、読み出し・書き込み・追加が可能になります。
open(某) or dieは、Perlの慣用句で「開けるか死ぬか」程度の意味に読めます。
モード
open関数の第2引数はモードです。
<
読出しモード
>
書込みモード
>>
追加書込みモード
の3つがあります。
2引数呼出し
上のコードではopen関数を3つの引数で書きましたが、下記のように2個の引数でも書けます。
open($handle, "> testperl.txt" );
と、ひとつの二重引用符の中にスペースで区切って書いても構いません。
クローズ
openで開いたファイルは、全ての操作が終わったら close 関数で閉じるます。
閉じ忘れるても、スクリプト終了時にインタープリターにより自動的に閉じられますが、明示的に閉じるとその時点でランタイムやオペレーションシステムのクローズ処理がはじまります。
クローズはファイルハンドルを保持する変数がスコープを抜けたとき、より正確に言うとファイルハンドルへの参照がなくなったときに行なわれます。
このためファイルハンドルは、my 宣言したレキシカルスコープの変数に保持するのが望ましいでしょう。
ファイルハンドル
open関数の第一引数(例では$handleの部分)は、ファイルハンドルです。
複数の入出力先を区別のためにファイルハンドルが必要になります。
Perlにかぎらず、C言語など他の多くのプログラム言語でも、若干の記法の違いこそあるものの、ファイルハンドルのようなものが存在しており(CではファイルディスクリプタやFILE型へのポインタにほぼ相当)、このファイルハンドルと似たような使い方です。
書き込み命令の方法
そして、書き込みをしたい場合、書き込みモードで開いたファイルに対し、
print ファイルハンドル名 "書き込みたい語句";
で書き込みできます。
たとえば上記コードの場合の
print $handle "zzzzz";
なら、ファイルtestperl.txt に「zzzzz」と書き込まれます。
実際にコマンド端末で上記コード例を実行して試してみましょう。たしかにファイルに書き込まれているハズです。
このような出力先を指定する方法でprint関数で外部ファイルに書き込みする方法は、Perlに特有の方法です。(C言語では、違う方法です。)

標準出力

編集

たとえば、

コード例
print STDOUT "qqq\n";
をコマンド端末で実行すると、単に「qqq」と表示されます。(行末の \n は改行のエスケープシーケンス)
STDOUT は、標準出力で、一般的にはコマンド端末画面のことです。
print関数はこのように出力先を指定する機能があります。特に出力先を指定しない場合には、printの出力先は標準出力になるので、今までの単元では気にすることなく print "文字列" のように利用できたわけです。

ファイルハンドル

編集

Perlには、次に挙げる特殊なファイルハンドルが組み込まれています。

STDIN
標準入力を表します。
STDOUT
標準出力を表します。
STDERR
標準エラー出力を表します。
DATA
__END__以降を表します。

ファイルハンドルから一行ずつ読み込むには、< >演算子を用います。

while (<STDIN>) {
    print;
}
これは次のコードと等価です。
while (defined($_ = readline *STDIN)) {
    print $_;
}
あるいは
print while <>;
空のファイルハンドルを指定すると標準入力から読み込みます。
ただし、コマンドライン引数がある場合はそれをファイル名として解釈し、ファイルの内容を標準入力にパイプした上で、標準入力から読み込みます。
while (<>) {
    print;
}

ファイルハンドルに対して出力するには、print()の間接ファイルハンドル記法を用います。

print STDOUT "Hello, world!\n";

ローカルファイルはopen()を呼び出してファイルハンドルに関連付けて操作します。

open my $fh, '<', '/etc/hosts' or die $!;
print while <$fh>;
close $fh;

グローバルスコープのスカラ変数でファイル名を与え、同名のファイルハンドルでファイルをオープンできます。

$FH = "/etc/hosts";
open FH;
print while <FH>;
close FH;
推奨できる書き方ではありませんが、過去に書かれたコードにこのパターンがある可能性はあるので、一つしか引数を取らないopen関数を見かけたら思い出してください。

コマンドラインオプション

編集

コマンドライン引数は、@ARGVという特殊な配列に代入されます。サブルーチンの外で空引数のshift()を呼び出すと、shift(@ARGV)と解釈されます。

my $arg = shift;
print "第一引数は $arg です。";

特殊変数$/

編集

特殊変数$/を指定すると入力レコードレパレータを変更することができます(デフォルトは\n)。次の例では、「hoge.txt」から一行ずつではなくすべての内容を一度に読み出す。

open my $fh, '<', 'hoge.txt'
    or die $!;
my $content = do { local $/; <$fh> };
close $fh;


正規表現

編集

Perlは強力な 正規表現 をサポートしています。正規表現とは、大まかにいうと、検索の機能を高度化しやすくしたものです。Perl以外のJavaやJavaSciptやPHPなども正規表現をサポートしていますが、Perlは古くから正規表現を本格的にサポートしています。 また、Perlの拡張正規表現は、Perl Compatible Regular Expressions( PCRE ) としてPerlの外でも使うことができるため、正規表現のディファクトスタンダードの1つとなっています。

パターンマッチング

編集

二項演算子 =~ は、左辺のスカラー式を右辺のパターンマッチに拘束します。 正規表現に関する操作では、デフォルトで $_ という文字列を検索したり変更したりします。 この演算子は、そのような操作を他の文字列に対して行うようにします。 右辺は、検索パターン (//)、置換 (s///)、または変換 (tr///) です。 左辺は、デフォルトの $_ の代わりに検索、置換、または変換されるはずのものです。 スカラーコンテキストで使用する場合、返り値は通常操作の成功を表します。 リストコンテキストでの動作は、特定の演算子に依存します。

検索
if ("Wikibooks" =~ /book/) {
  print "Match!";
} else {
  print "Not match.";
}
実行結果
Match!
書式
$検索対象の変数 =~ /検索したい文字列/
検索したい文字列を、変数が内部に含んでいるかどうかを調べる際に、 =~ および /RE/ を使います。上記のコードは真偽を返すので、主に条件分岐を行う際に用いられます。

!~は、=~の論理反転バージョンです。

コード例
if ("Wikibooks" !~ /books/) {
  print "Not match.";
} else {
  print "Match!";
}
実行結果
Match!
/.../ 内ではダブルクォーテーション ("...") と同じく、変数やエスケープシーケンスが評価されます。
コード例
$bar = "books";
"Wikibooks" =~ /$bar/ # マッチする。

置換

編集

=~演算子を使用すると、右辺に s/// を取ることで左辺の文字列を置換することができます。

コード例
my $string = "Hello, world!";
$string =~ s/world/Perl/; # "world"を"Perl"に置換
print $string; # 出力は "Hello, Perl!"

上記の例では、$string内の "world" を "Perl" に置換しています。

変換

編集

=~演算子を使用すると、右辺に tr/// を取ることで左辺の文字列を変換することができます。

コード例
my $string = "hello world";
$string =~ tr/a-z/A-Z/; # アルファベットを大文字に変換
print $string; # 出力は "HELLO WORLD"

この例では、$string内のアルファベットを小文字から大文字に変換しています。

パターンの区切り文字

編集

m を前置すると正規表現を囲う記号にスラッシュ以外を用いることができます。

[ ], ( ), < >, { } などの括弧は開き括弧と閉じ括弧が対応するように用います。
$foo =~ m/bar/;
$foo =~ m#bar#;
$foo =~ m@bar@;
$foo =~ m!bar!;
$foo =~ m{bar};
$foo =~ m(bar);

囲み記号にシングルクォーテーションを用いると、変数やエスケープシーケンスが評価されるのを防ぐことができます。

$foo = "books";
"Wikibooks" =~ m/$foo/; # マッチする
"Wikibooks" =~ m'$foo'; # マッチしない

正規表現における文字クラスの構文と使用方法

編集

文字クラスは、文字の集合を表現し、その集合の中の1文字にマッチする方法です。 重要なのは、文字クラスのマッチングがソース文字列のちょうど1文字を消費するということです(ソース文字列とは、正規表現がマッチされる文字列のことです)。

文字クラス

編集

Perlの正規表現には、ドットバックスラッシュ・シーケンス角括弧で囲まれた文字クラスの3種類の文字クラスがあります。

しかし、「文字クラス」という用語は、しばしば角括弧で囲まれた形式だけを意味するものとして使われることがあるので、文脈によりどちらを表しているのか注意が必要です。

ドット

編集

ドット . は、デフォルトでは、改行を除く全ての1文字にマッチします。

コード例
if ("dog cat" =~ /..g/) {
  print "Match!";
} else {
  print "Not match.";
}
実行結果
Match!
例えば、「..g」は文字列の末尾が "g" である3文字の単語(他の2文字目は任意)にマッチします。したがって、"dog" はこの条件を満たすのでマッチします。
しかし、ドットを1つ追加した場合、マッチしなくなります。なぜなら、"g" の前には2文字しかないためです。
コード例
if ("dog cat" =~ /...g/) {
  print "Match!";
} else {
  print "Not match.";
}
実行結果
Not Match.
dogの「g」の前には1文字以上の文字があるので、ドットが1つでもマッチします。
コード例
if ("dog cat" =~ /.g/) {
  print "Match!";
} else {
  print "Not match.";
}
実行結果
Match!

メタキャラクター

編集

正規表現において特殊な意味を持つ以下の文字をメタキャラクターと呼びます。

+ * ? . ( ) [ ] { } | \ $ ^

これらの文字自身を表すには \+, \* のようにバックスラッシュでエスケープします。

アンカー

編集

「アンカー」とは長さを持たない正規表現です。

代表的なものに、文字列先頭にマッチする ^、文字列末尾にマッチする $ があります。

書式
"Wikibooks" =~ /^Wiki/; # マッチする
"Wikibooks" =~ /books$/; # マッチする
"Wikibooks" =~ /Wiki$/; # マッチしない
コード例
if ("Wikibooks" =~ /^Wiki/) {
  print "Match!";
} else {
  print "Not match.";
}
実行結果
Match!

文字列「Wiki」は検索対象の先頭にあるのでマッチします。

if ("Wikibooks" =~ /^books/) {
  print "Match!";
} else {
  print "Not match.";
}
実行結果
Not Match.

文字列「books」は検索対象に含まれていますが、先頭にはないのでマッチしません。

他に、単語の境界(正確には、単語の先頭あるいは末尾)にマッチする \b、それ以外の部分にマッチする \B があります。つまり、その位置に半角スペースまたは単語の先頭あるいは終わりがある場合にマッチします。


書式
"dog cat"  =~ /a\b/; # マッチしない
"dog cat" =~ /g\b/; # マッチする
コード例
if ("dog cat" =~ /g\b/) {
  print "Match!";
} else {
  print "Not match.";
}
実行結果
Match!

dogのgの後ろに半角スペースがあるので、結果はマッチです。


コード例
if ("dog cat" =~ /a\b/) {
  print "Match!";
} else {
  print "Not match.";
}
実行結果
Not Match.

catのなかに「a」がありますが、位置が単語の境界ではないので、マッチしません。

バックスラッシュ・シーケンス

編集

バックスラッシュ・シーケンスとは、最初の1文字がバックスラッシュである文字の並びのことです。Perlはこのような多くのシーケンスに特別な意味を持たせており、そのうちのいくつかは文字クラスになっています。つまり,その文字がシーケンスで定義された特定の文字集合に属していれば,それぞれ1つの文字にマッチします。

バックスラッシュ・シーケンス
記号 意味
\d 10進数文字にマッチします。
\D 10進でない桁の文字にマッチします。
\w 単語文字にマッチします。
\W 単語以外の文字にマッチします。
\s 空白文字にマッチします。
\S 非空白文字にマッチします。
\h 横の空白文字にマッチします。
\H 横書き以外の文字にマッチします。
\v 縦書き空白文字にマッチします。
\V 縦書き空白でない文字にマッチします。
\N 改行でない文字にマッチします。
\pP,\p{Prop} 指定された Unicode プロパティを持つ文字にマッチします。
\PP,P{Ptop} 指定された Unicode プロパティを持たない文字にマッチします。

角括弧で囲まれた文字クラス

編集

[] の内側をブラケットで囲んだ部分は「角括弧で囲まれた文字クラス」です。

書式
"Wikibooks" =~ /[abc]/   # a と c は含まれないが、b は含まれるのでマッチする
コード例
if ("Wikibooks" =~ /[abc]/) {
  print "Match!";
} else {
  print "Not match.";
}
実行結果
Match!
  • 文字クラスのハイフン

[] の中でハイフン - を使うことで、文字の範囲を指定することができます。

たとえば、/[a-z]/ と書けば、英語の小文字にマッチします。(なお Perl の正規表現では、大文字と小文字を区別します。)

あるいは /[0-9]/ と書けば、十進数の数字にマッチします。


書式
$foo =~ /[abc]/;
$foo =~ /[a-c]/;   # 上の文と等価
$foo =~ /[01234]/;
$foo =~ /[0-4]/;   # 上の文と等価

ハイフンの前の文字は、後ろの文字よりも文字コードにおいて前でなければなりません。

書式
$foo =~ /[b-a]/; # エラー
$foo =~ /[5-3]/; # エラー
$foo =~ /[a-3]/; # エラー
コード例
if ("Wikibooks" =~ /[a-c]/) {
  print "Match!ggg";
} else {
  print "Not matchggg.";
}
実行結果
Match!
エスケープ

ハイフン自体を文字クラスに含めるには、文字クラスの一番前か一番後ろに記述するか、バックスラッシュでエスケープします。(なお、Windows環境では表示で、バックスラッシュの代わりに、通貨記号の円マークが表示されるかもしれません。)

書式
$foo =~ /[-ab]/;  # ハイフンまたは a か b にマッチする
$foo =~ /[ab-]/;  # 上の文と等価
$foo =~ /[a\-b]/; # 上の文と等価

開きブラケット ([) に ^ を後置すると、否定キャラクタクラスを表現することができます。

否定キャラクタクラス
$foo =~ /[^0-9]/; # 数字以外にマッチする

グループ化

編集

丸括弧で括った部分はグループ化されます。

グループ化した部分は後から参照することができます。これを後方参照といいます。 同じ正規表現内で後方参照を行うには、\1, \2... を用います。

後方参照
$foo =~ /(abc)\1/; # abc が2回連続する文字列にマッチする

また、正規表現外で後方参照を行うには、スカラー変数 $1, $2... を用います。

$1をつかった一致部分参照
"Wikibooks" =~ /(wiki)/i;
print $1; # 'Wiki' と出力される。

選択

編集

縦線を用いると正規表現を選択することができます。

パターンの選択
$foo =~ /abc|def/; # abc あるいは def にマッチする
コード例
$foo = "defabc" ;
if ($foo =~ /abc|def/) {
  print "Match!";
} else {
  print "Not match.";
}
実行結果
Match!

下記の書式例では、^$| よりも優先順位が高いため、abc で始まる文字列か def で終わる文字列にマッチします。

コード例
$foo =~ /^abc|def$/;
コード例
$foo = "defabc" ;
if ($foo =~ /^abc|def$/) {
  print "Match!";
} else {
  print "Not match.";
}
実行結果
Not Match.


コード例
$foo = "abcdef" ;
if ($foo =~ /^abc|def$/) {
  print "Match!";
} else {
  print "Not match.";
}
実行結果
Match!

先頭と末尾の両方に接する「"abc"」か 「"def"」だけにマッチさせるには以下のようにします。

# カッコで両項ともククる方式
$foo =~ /^(abc|def)$/;

# あるいは各項に展開する方式
$foo =~ /^abc$|^def$/;


コード例
$foo = "defabc" ;
if ($foo =~ /^abc$|^def$/) {
  print "Match!";
} else {
  print "Not match.";
}
実行結果
Not Match.
コード例
$foo = "def" ;
if ($foo =~ /^abc$|^def$/) {
  print "Match!";
} else {
  print "Not match.";
}
実行結果
Match!


コード例
$foo = "def555" ;
if ($foo =~ /^abc$|^def$/) {
  print "Match!";
} else {
  print "Not match.";
}
実行結果
Not Match.

置き換え

編集

s///演算子を用いると、文字列の置換を行うことができます。

$str = "Wikibooks";
$str =~ s/books/pedia/; # $str は "Wikipedia" になる

m//と同じく、スラッシュ以外の記号を用いることもできます。

<pre><nowiki>$foo =~ s/foo/bar/;
$foo =~ s#foo#bar#;
$foo =~ s@foo@bar@;
$foo =~ s!foo!bar!;
$foo =~ s{foo}{bar};
$foo =~ s(foo)(bar);</nowiki></pre>

修飾子

編集

正規表現のメタキャラクター、あるいはパターンマッチそのものの振る舞いを変えるために、修飾子を指定することができます。たとえば、正規表現がアルファベットの大文字小文字を区別せずにマッチするようにするためには、

m/^perl$/i; # perlやPerl、PERL、PeRl、pErLなどにマッチする。

のように、最後のスラッシュ(あるいは何らかの記号)の後に、i修飾子を付加します。

Perlの正規表現の修飾子
i 大文字小文字の同一視 (case-insensitive)
s 「.」が改行にもマッチするようにする (single line)
m 複数行として扱う (multi-line)
x 拡張正規表現を使う (extended)
e Perlのコードとして評価する (evaluation)
ee Perlのコードとして2回評価する (evaluation and evaluation)
g 連続して何回もマッチ (global)
o 一度だけコンパイルする (only once)

拡張正規表現

編集

x修飾子を付けると正規表現内の空白や改行が無視され、「#」以降はコメントとして扱われます。

# 1と出力
print 1 if "Apple" =~ /
A
p
p
l
e
/x;

アトムとアサーション

編集

「ABC」や「[0-9]」、「.*?」のように、何かにマッチする正規表現の構文をアトムといいます。最後の「.*?」は、アトム「.」に量指定子「*」、「?」が付いたもので、特に量指定子付きアトムといいます。

「^」や「$」、「|」のように、何かにマッチするわけではない正規表現の構文をアサーションといいます。

正規表現の構文は基本的にアトムとアサーションのどちらかに分けられます。ただし、\Qや\E、\uや\Uのような特殊なシーケンスはアトムでもアサーションでもありません。これらは構文のふるまいを変えるものです。

拡張構文

編集

コメント

編集
/(?#ここはコメント)/

クラスタ化専用カッコ

編集

(?:...)はクラスタ化(正規表現をまとめること)のみに使われるカッコです。キャプチャを行わないため、マッチした部分を正規表現の中で\1、\2のように参照したり、後から$1、$2のような変数で参照したりすることができません。キャプチャを行う必要がない場合は、このカッコを使うことで効率化を図ることができます。

"Apple" =~ /^(?:Apple|Banana|Cherry)$/; # AppleかBananaかCherryにマッチ

これにはimsx修飾子を付けることもできます。i修飾子を付けるには、i-msx(iを指定しmsxを指定しない)とします。

"aPpLe" =~ /^(?i-msx:Apple|Banana|Cherry)$/;

単に修飾子を有効または無効にするためだけにこのカッコを使うこともできます。

/A(?i-msx)B/; # Aは大文字小文字を区別するが、Bは区別しない

ルックアラウンドアサーション

編集

ルックアラウンドアサーションとは、直後または直前にパターンが出現すること、あるいは出現しないことを確認し、確認するだけで何にもマッチしないアサーションです。

  • 肯定先読み

直後にPATTERNが出現することを確認します。

/(?=PATTERN)/
  • 否定先読み

直後にPATTERNが出現しないことを確認します。

/(?!PATTERN)/

次の例では、「&amp;」以外の「&」をすべて「&amp;」に置換します。

$str =~ s/&(?!amp;)/&amp;/g;
  • 肯定後読み

直前にPATTERNが出現することを確認します。

/(?<=PATTERN)/
  • 否定後読み

直前にPATTERNが出現しないことを確認します。

/(?<!PATTERN)/

非バックトラックサブパターン

編集

バックトラックしないPATTRENにのみマッチします。

(?>PATTERN)

コードサブパターン

編集

(?{ CODE })という形で、正規表現の中にPerlのコードを埋め込むことができます。

/(?{ print "Hello, world!\n" })/; # Hello, world!と表示

(??{ CODE })という形では、CODEを評価した結果得られた正規表現にマッチします。

"ABC" =~ /^(??{ "A"."B"."C" })$/; # ABCにマッチする

条件付き展開

編集

Perlの条件演算子?:のように、条件が真か偽かでマッチさせるパターンを変えることができます。

/(?(COND)TRUE|FALSE)/

または

/(?(COND)TRUE)/

CONDが真の場合はTRUE、偽の場合はFALSEのパターンにマッチします。


変換

編集


リファレンス

編集

Perlのリファレンスは、変数やデータ構造への間接的なアクセスを提供するための仕組みです。スカラー、配列、ハッシュなどのデータ型のリファレンスを作成することで、これらのデータ構造を関数間で渡したり、複数の変数に同じデータを参照させることができます。リファレンスは、\ 演算子を使って作成され、デリファレンス(リファレンスから元のデータにアクセスすること)には特定の記号 ($, @, %) を使用します。配列のリファレンスは @ ではなく [] でアクセスします。

リファレンスの例
use v5.30.0;

my $str = "ABC to Z";
my @ary = (1,2,3);
my %hash = ( a => 1, b => 2);
sub code { "hello!" }
open(my $fh, '<', "/dev/null") or die "Could not open /dev/null for reading";

foreach my $ref(\$str, \@ary, \%hash, \&code, qr/[A-Z]/, \*str, \$fh) {
    my $rtype = ref $ref;
    if ($rtype eq "SCALAR") {    say qq($rtype: $$ref ) }
    elsif ($rtype eq "ARRAY") {  say qq($rtype: @$ref ) }
    elsif ($rtype eq "HASH") {   say qq($rtype: @{[ %$ref ]} ) }
    elsif ($rtype eq "CODE") {   say qq($rtype: @{[ &$ref ]} ) }
    elsif ($rtype eq "Regexp") { say qq($rtype: @{[ $$ref ]} ) }
    elsif ($rtype eq "GLOB") {   say qq($rtype: @{[ *$ref ]} ) }
    elsif ($rtype eq "REF")  {   say qq($rtype: @{[ *$$ref ]} ) }
    else                     {   say qq($rtype: ) }
}

close $fh;
実行結果
ARRAY(0x560a821949b8) ARRAY(0x560a821a2688)
SCALAR: ABC to Z 
ARRAY: 1 2 3 
HASH: a 1 b 2 
CODE: hello! 
Regexp: (?^u:[A-Z]) 
GLOB: *main::str  
REF: *main::$fh

多次元配列

編集

配列の中に配列が入っているものを多次元配列といいます。

Perlで多次元配列を生成するには、配列を要素にしたリストで配列を初期化すると・・・

配列を要素にしたリストで配列を初期化
#!/usr/bin/perl
use v5.30.0;
use warnings;

my @x = qw(A B C);
my @y = qw(X Y Z);
my @ary = ( @x, @y );

say "@ary"
実行結果
A B C X Y Z
@xと@yが展開され、単純な配列(一次元配列)になっています。
配列へのリファレンスを要素にしたリストで配列を初期化
#!/usr/bin/perl
use v5.30.0;
use warnings;

my @x = qw(A B C);
my @y = qw(X Y Z);
my @ary = ( \@x, \@y );

say "@ary";
say "@$_" foreach @ary
実行結果
ARRAY(0x560a821949b8) ARRAY(0x560a821a2688)
A B C 
X Y Z
\@xのように、配列の接頭辞「@」の前に「\」を付けます。

最初に示した例では、配列をリストの要素にしようとしましたが、Perlのリストは与えられた複合的なデータ構造を展開する性質があります。 この性質は、Perlの配列はコピーや関数やサブルーチンの引数として渡すときに(参照わたしでなく)値わたしであることと関連しています。 Perlの配列の代入は、他の多くの動的な型を持つプログラミング言語と違い、別名の作成ではなく要素ごとのコピーです。

リストは与えられた複合的なデータ構造を展開する
#!/usr/bin/perl
use v5.30.0;
use warnings;

my @x = ("A", "B", "C");
my %y = (a => 1, b => 2);
my @ary = (@x, %y);
say "@ary";
say join ", ", @ary
実行結果
A B C b 2 a 1 
A, B, C, b, 2, a, 1
多次元配列のような複雑な構造を作るためには、\@xとして、@xという配列を間接的に指示すことで実現します。

リファレンス

編集

リファレンスとは、あるデータが格納されている場所を指し示すデータ型です。データに「\」を前置して生成します。

#!/usr/bin/perl
use v5.30.0;
use warnings;

my $x = 42;
my $y = \$x;
say $y;
say $$y;
say ref $y;

$$y = 4423;
say $$y;
say $x;

my @ary = qw(1 2 4 8);
my $ref = \$ary[1];
say "@ary";
$$ref = 0;
say "@ary";
実行結果
SCALAR(0x5633528ba878)
42
SCALAR
4423
4423
1 2 4 8 
1 0 4 8
SCALARというのは、$xのデータ型がスカラーであることを表しています。
(0x55c487fbf9b8)は、$xが格納されている場所(メモリアドレス)を表しています。
なお、0x55c487fbf9b8という数値は環境によって異なります。
リファレンス$yを使って値を参照するには、参照先のオブジェクトのデータ型に対応した接頭辞(この場合はスカラーなので \)を補い$$yとします。
組込み関数 ref はリファレンス先のデータ型を返し、リファレンスでないものを引数にすると undef がかえります。
$y$xのリファレンスな状態で、$$yを書換えると$xが書換わります。
配列の要素へのリファレンスを取り、リファレンスを経由して配列(の要素)を参照/変更をすることもできます。
リファレンスはこのように、「間接的に値を参照する」仕組みであり、副次的な効果として「別名関係」を作ることになります。

スカラーだけでなく、様々なデータ型のリファレンスを生成することができます。

\42; # スカラーのリファレンス
\$x; # スカラーのリファレンス
\@x; # 配列のリファレンス
\%x; # ハッシュのリファレンス
\&x; # サブルーチンのリファレンス
\*x; # 型グロブのリファレンス
\\$x; # スカラーのリファレンスのリファレンス
「\」はリファレンスを生成するための単項演算子です。リファレンスそのものはスカラーの一種です。
$@%などの接頭辞を前置してリファレンスから元のデータを取り出すことを、デリファレンスといいます。
リファレンスはC言語のポインタに似ていますが、より抽象的で安全だとされています。
とはいえ、スコープを外れたオブジェクトのリファレンスを使った参照をすることができるなど、フリーハンドな安全が手に入るわけではありません。
リファレンスは、配列の配列などの複雑なデータ構造を扱う場合や、オブジェクトを扱う場合、サブルーチンに参照渡しを行う場合などに使われます。
クラスのコンストラクターは、
通常の配列やハッシュを使えば済む場面で、リファレンスを使うことはありません。

データを百科事典の記事の内容だとすれば、リファレンスはその記事が「何ページ目にあるか」に当たります。

スカラーのリファレンス

編集
#!/usr/bin/perl
use v5.30.0;
use warnings;

my $x = \42;
my $y = \$x;

print <<EOS;
\$x --> $x
ref \$x --> @{[ ref $x ]}
\${ \$x } --> ${ $x }
\$\$x --> $$x
\$y --> $y
\$\$y --> $$y
ref \$y --> @{[ ref $y ]}
ref \$\$y --> @{[ ref $$y ]}
\${ \${ \$y } } --> ${ ${ $y } }
\$\$\$y --> $$$y
EOS
実行結果
$x --> SCALAR(0x5588e7e33928)
ref $x --> SCALAR
${ $x } --> 42
$$x --> 42
$y --> REF(0x5588e7e339b8)
$$y --> SCALAR(0x5588e7e33928)
ref $y --> REF
ref $$y --> SCALAR
${ ${ $y } } --> 42 
$$$y --> 42

配列のリファレンス

編集
#!/usr/bin/perl
use v5.30.0;
use warnings;

my @x = ("A", "B", "C");
my $y = \@x;
print <<EOS;
\@x --> @x
\$y --> $y
\@\$y --> @$y

EOS

my $x = ["A", "B", "C"];
print <<EOS;
\$x --> $x
\@\$x --> @$x
\$x->[0] --> $x->[0]
\$x[0] --> $x[0]

EOS

$x = ["A", ["B"], "C"];
print <<EOS;
\$x --> $x
\@\$x --> @$x
\$x->[1]->[0] --> $x->[1]->[0]
\$x->[1][0] --> $x->[1][0]

EOS

$x = ["A"];
print <<EOS;
\$x --> $x
\@\$x --> @$x
\$x->[0] -->  $x->[0]
\${ \$x }[0] --> ${ $x }[0]
\$\$x[0] --> $$x[0]

EOS

$x = [["A"]];
print <<EOS;
\$x --> $x
\@\$x --> @$x
\$\@\$x --> $@$x
\$\@\$x[0] --> $@$x[0]
\$x->[0][0] -->  $x->[0][0]
\${ \$x }[0][0] --> ${ $x }[0][0]
\$\$x[0][0] --> $$x[0][0]
EOS
実行結果
@x --> A B C
$y --> ARRAY(0x55fade5819b8)
@$y --> A B C

$x --> ARRAY(0x55fade581358)
@$x --> A B C
$x->[0] --> A
$x[0] --> A

$x --> ARRAY(0x55fade5814a8)
@$x --> A ARRAY(0x55fade581430) C
$x->[1]->[0] --> B
$x->[1][0] --> B

$x --> ARRAY(0x55fade5814d8)
@$x --> A
$x->[0] -->  A
${ $x }[0] --> A
$$x[0] --> A

$x --> ARRAY(0x55fade581430)
@$x --> ARRAY(0x55fade581358)
$@$x --> ARRAY(0x55fade581430)
$@$x[0] --> A
$x->[0][0] -->  A
${ $x }[0][0] --> A 
$$x[0][0] --> A
$x->[0]$x[0]と略記できます(リファレンス経由の配列参照という意図がなくなるので、残すべきとの主張もあります)。

ハッシュのリファレンス

編集

ハッシュのリファレンスを生成するには、次のようにします。

#!/usr/bin/perl
use v5.30.0;
use warnings;

my %x = ( a => "Apple", b => "Banana", c => "Cherry" );
my $y = \%x;
print <<EOS;
\%\$y --> @{[ %$y ]}
\$y->{"a"} --> $y->{"a"}
\$y->{c} --> $y->{c}

EOS

my $x = { a => { e => "Apple", t => "Apricot" }, b => "Banana", c => "Cherry" };
print <<EOS;
\$x --> @{[ %$x ]}
\%\$x --> @{[ %$x ]}
keys \%\$x --> @{[ keys %$x ]}
values \%\$x --> @{[ values %$x ]}
\$x->{"a"} --> $x->{"a"}
\$x->{a} --> $x->{a}
\$x{a} --> $x{a}
\$x->{a}->{t} --> $x->{a}->{t}
\$x->{a}{t} --> $x->{a}{t}
EOS
実行結果
%$y --> a Apple c Cherry b Banana
$y->{"a"} --> Apple
$y->{c} --> Cherry

$x --> a HASH(0x55e8aa9af358) b Banana c Cherry
%$x --> a HASH(0x55e8aa9af358) b Banana c Cherry
keys %$x --> a b c
values %$x --> HASH(0x55e8aa9af358) Banana Cherry
$x->{"a"} --> HASH(0x55e8aa9af358)
$x->{a} --> HASH(0x55e8aa9af358)
$x{a} --> Apple
$x->{a}->{t} --> Apricot
$x->{a}{t} --> Apricot
$x->{a}->{t}$x->{a}と書くことが出来ますが、$x->{a}$x{a}と書くとHashをListに展開され意図した結果になりません。

サブルーチンへのリファレンス

編集

サブルーチンへのリファレンスを生成するには、次のようにします。

サブルーチンへのリファレンスの生成と呼出し
#!/usr/bin/env perl
use v5.20.0;
use warnings;

sub say_hello {
  say 'Hello'
}
my $ref = \&say_hello;

$ref->();
&$ref;
実行結果
Hello
Hello
サブルーチンへのリファレンスを得る場合、サブルーチン名の前に \& を付けます。
サブルーチンへのリファレンスからサブルーチンを呼出すときは、
$ref->();
->演算子で参照し、通常のサブルーチンと同じく実引数は()の中に書き渡します。
&$ref;
&を前置すれば->を省略でき、引数がなければ () も省略できます。

また、次のように書くことで、サブルーチンへのリファレンスを直接書くこともできます(クロージャや、無名サブルーチンとも呼ばれます)

my $ref = sub { ... };

サブルーチンへのリファレンスには特別な性質があり、サブルーチンへのリファレンスを生成した時の環境を保存します。

クロージャー
#!/usr/bin/env perl
use v5.20.0;
use warnings;

sub print_hello_name {
  my $name = shift;
  return sub { say "Hello $name!!" };
}
my $ref1 = print_hello_name('Kenta');
my $ref2 = print_hello_name('Taro');
$ref1->();
&$ref2;
say ref $ref1;
say ref $ref2;
実行結果
Hello Kenta!!
Hello Taro!!
CODE 
CODE
サブルーチンの中で参照される変数の値は、サブルーチンが定義されたものが参照されます。
組込み関数 ref を使うと、リファレンスの参照先のデータ型を表示できます。

正規表現のリファレンス

編集

他のリファレンス生成と少しやり方が異なりますが、正規表現のリファレンスを生成することも可能です。 正規表現のリファレンスを作るには、正規表現の前にqrを付けます。

my $regex = qr/\.pm$/; # 文字の最後に「.pm」が付いている文字にマッチする正規表現のリファレンスを生成

正規表現のリファレンスを使うには、次のようにします:

#!/usr/bin/perl
use v5.30.0;
use warnings;

# 引数の最後に「.pm」が付いているかどうかを調べるサブルーチン
#!/usr/bin/perl
use v5.30.0;
use warnings;

# 引数の最後に「.pm」が付いているかどうかを調べるサブルーチン
sub match_pm {
  my $name = shift;
  my $regex = qr/\.pm$/;
  if ($name =~ /$regex/) {
    say "$name -- match"
  } else {
    say "$name -- mismatch"
  }
}
match_pm('test.pm');
match_pm('test.pl');
match_pm('testpm');

my $re = qr/\.pm$/;
say ref $re;
say $$re
実行結果
test.pm -- match
test.pl -- mismatch
testpm -- mismatch
Regexp
(?^u:\.pm$)
置換は、s/$regex/置換後の文字/g としますが、この場合はリファレンス私にしないと結果を呼出し元に反映できません。

オブジェクト指向におけるリファレンス

編集


クラス

編集


クラス

編集

Perl5.36までは、blessを使ったパッケージベースのオブジェクト指向が採用されていました。 Perl5.38からは、新たにキーワードclassが導入され、より一般的なクラスベースのオブジェクト指向言語のスタイルでコードを書けるようになりました。 また、class構文はblessの糖衣構文ではなく、新たなオブジェクト機構を提供しています。

classの宣言と利用

編集
use v5.38;
use feature 'class';
no warnings 'experimental::class';

class Hello {
  field $name :param;
  
  method say_hello {
    say "Hello, $name!";
  }
}

my $hello = Hello->new(name => "Universe");
$hello->say_hello;
実行結果
Hello, Universe!
このコードは、Perl 5.38を使用してクラスを使った"Hello, World!"の例です。
まず、use v5.38;はPerlのバージョンを5.38に指定しています。次に、use feature 'class';は、Perl 5.0から実験的に導入されたクラス機能を有効にしています。さらに、no warnings 'experimental::class';は、クラス機能に関する実験的な警告を無効にしています。
class Hello { ... }のブロックで、Helloという名前のクラスを定義しています。
field $name :param;は、クラスのフィールドである$nameを定義しています。:paramは、$nameがコンストラクタの引数であることを示しています。
method say_hello { ... }のブロックで、say_helloというメソッドを定義しています。
say "Hello, $name!";は、"Hello, $name!"というメッセージを表示しています。$nameはフィールドの値を参照しています。
my $hello = Hello->new(name => "Universe");では、Helloクラスのインスタンスを作成しています。コンストラクタには、nameというキーワード引数とその値が渡されています。
最後の行の$hello->say_hello;では、$helloインスタンスのsay_helloメソッドを呼び出しています。この結果、"Hello, Universe!"という出力が得られます。
packeageとblessを使った等価な実装例
use v5.30;
use feature 'signatures';
no warnings "experimental::signatures";

package Hello {
  sub new: prototype($$)($class, $name) {
    bless {
      name => $name,
    }, $class;
  }

  sub say_hello($self) {
    say "Hello, @{[ $self->{name} ]}!"
  }
}

my $hello = Hello->new("Universe");
$hello->say_hello;
実行結果
Hello, Universe!
このコードは、Perl 5.30を使用してオブジェクト指向を使った"Hello, World!"の例です。
最初に、use v5.30;を使用してPerlのバージョンを5.30に指定しています。次に、use feature 'signatures';は、サブルーチンに引数の型指定を行うための機能を有効にしています。さらに、no warnings "experimental::signatures";は、引数の型指定に関する実験的な警告を無効にしています。
package Hello { ... }のブロックで、Helloという名前のパッケージ(クラス)を定義しています。
sub new: prototype($$)($class, $name) { ... }は、newというサブルーチン(コンストラクタ)を定義しています。prototype($$)は、引数の型指定を行っています。$class$nameはそれぞれクラス名と名前の引数を表しています。bless関数を使用して、nameフィールドを持つハッシュリファレンスを作成し、それをクラスに対してブレス(関連付け)しています。
sub say_hello($self) { ... }は、say_helloというサブルーチン(メソッド)を定義しています。$selfは、メソッドが呼び出されるインスタンス自体を表します。$self->{name}を参照して、"Hello, $self->{name}!"というメッセージを表示しています。
my $hello = Hello->new("Universe");では、Helloクラスのインスタンスを作成しています。引数として"Universe"が渡されます。
最後の行の$hello->say_hello;では、$helloインスタンスのsay_helloメソッドを呼び出しています。この結果、"Hello, Universe!"という出力が得られます。
小まとめ
  • パッケージ宣言とは異なり、クラス宣言ではclassというキーワードを使用します。
  • クラス宣言はブロック構文または文構文のいずれかで行うことができます。
  • クラス宣言内で、fieldというキーワードを使用してフィールド(クラスのインスタンス変数)を宣言できます。フィールドはクラスのスコープ内でのみ参照可能であり、各インスタンスごとに独自のフィールドの値を持ちます。
  • クラス宣言内で、methodというキーワードを使用してメソッド(クラスのサブルーチン)を宣言できます。メソッドはクラスのインスタンス内で呼び出されることを意図しており、自動的に特殊変数$selfが生成され、メソッド内で使用されます。
  • クラスやフィールドには属性を指定することができます。例えば、クラスの継承を指定する:isa属性や、フィールドの初期化を制御する:param属性があります。
これらの機能を使用することで、Perlにおいてよりオブジェクト指向プログラミングのパラダイムに則ったコーディングが行えるようになります。
ただし、この機能は実験的なものであり、将来のバージョンで変更されたり、削除されたりする可能性があります。最新のリリースノートや公式ドキュメントを参照することをおすすめします。

特殊コードブロック

編集

ADJUST

編集

ADJUSTブロックは、Perlのクラス構文で使用される特殊なブロックです。このブロックはクラスの宣言内で定義され、オブジェクトの構築中に実行されるカスタムコードを含めるために使用されます。 ADJUSTブロックは、クラスのフィールド(インスタンス変数)やメソッドが初期化される前に実行されます。つまり、オブジェクトが構築される過程でフィールドの値を調整するために使用されます。

以下にADJUSTブロックの基本的な構文を示します:

class MyClass {
    field $field1;
    field $field2;
    
    ADJUST {
        # フィールドの値を調整するカスタムコード
    }
    
    method my_method {
        # メソッドの実装
    }
}

ADJUSTブロックは、クラス内でフィールドの初期値を設定したり、関連するフィールドの相互作用を制御したりするために使用されます。例えば、複数のフィールドの初期値に基づいて他のフィールドを計算する場合に便利です。 ADJUSTブロック内では、特殊変数「$self」を介して現在のオブジェクトインスタンスにアクセスすることができます。また、ADJUSTブロック内で定義された変数は、そのブロック内でのみ有効です。 ADJUSTブロックは、クラスの宣言内で複数回定義することができます。その場合、定義された順序に従って順番に実行されます。 なお、ADJUSTブロックはPerl 5.38.0から導入された実験的な機能であり、将来のバージョンで変更される可能性があります。最新のリリースノートや公式ドキュメントを確認することをおすすめします。

class構文で実装予定でv5.38では未実装な機能
class構文はまだ実験的であり、非常に不完全です。以下のリストは、追加や変更が必要な作業の一部の概要を示しています。
ロール
ロールを宣言するためのいくつかの構文(おそらくroleキーワード)と、クラスにロールを組み込むための構文(おそらく:does()属性)。
ADJUSTブロックへのパラメータ
ADJUSTブロックが名前付きパラメータを受け取ることができることを宣言するための構文。これらのパラメータはクラスのコンストラクタのAPIの一部になります。これは、サブルーチンのシグネチャに名前付き引数を追加するという似たような計画に触発されたものかもしれません。
class X { ADJUST (:$alpha, :$beta = 123) { ... } }
my $obj = X->new(alpha => 456);
ADJUSTブロックの真のブロック化
現在、各ADJUSTブロックは独自のCVでラップされ、フルのENTERSUBオーバーヘッドを伴って呼び出されます。すべてのフィールド初期化式が同じCV内に表示される仕組みを使って、ADJUSTブロックも同様に真のブロックとして使用できるようにすることができます。これにより、クラスに複数のADJUSTブロックがある場合に、呼び出しを高速化することができます。
アクセッサ生成属性
フィールドに対してアクセッサメソッドの生成を要求する属性。おそらく:readerと:writerです。
class X { field $name :reader; }
次のものと同等です。
class X { field $name; method name { return $name; } }
メタプログラミング
メタプログラミングAPIの拡張(現在RFC0022で提案されている)で、クラス、メソッド、フィールド、ADJUSTブロックなどのクラスに関連する詳細を追加します。
拡張のカスタマイズ
外部のモジュールがクラスシステムとやり取りする方法を提供する方法。これには、新しいクラスやフィールド属性を提供する能力が含まれます。

プラグマ

編集
use feature 'class';

warnings

編集
no warnings 'experimental::class';


モジュールとオブジェクト指向

編集


パッケージ

編集

Perl4までは、全ての変数は動的で単一のグローバルな名前空間に存在していました。 これは丁度 BASIC と同じ状況で、識別子の衝突の回避がプログラミングの大きなテーマでした。

この問題を解決するためにPerl5では

  1. vars プラグマや our キーワードを使って公開される名前空間付きグローバル変数
  2. my や state で宣言されたレキシカルスコープ変数

が導入されました。

完全修飾形式

編集

グローバル変数は、名前空間の一部とみなされ、「完全修飾形式」( fully qualified form )でアクセスできます。 逆に、レキシカルスコープ変数は、そのレキシカルスコープの一部とみなされ、「完全修飾形式」を持ちません。

完全修飾形式
名前空間::識別子

package

編集

Perl の名前空間は「パッケージ」と呼ばれ、package 宣言は変数や非限定動的名の前にどの名前空間を付けるかを決めます。

package 宣言のスコープは宣言に伴うブロック、ブロックを伴わない場合は次のpackage 宣言までです。

package 宣言を含むコード
use v5.20.0;

say "default package name is @{[ __PACKAGE__ ]}";

package PKG0 {
  sub f { say "I'm @{[ __PACKAGE__ ]}" }
}

say "In @{[ __PACKAGE__ ]}";

package PKG1;
sub f { say "I'm @{[ __PACKAGE__ ]}" }

say "In @{[ __PACKAGE__ ]}";

package main;
sub f { say "I'm @{[ __PACKAGE__ ]}" }

&PKG0::f;
&PKG1::f;
&main::f;
&::f;
&f;
実行結果
default package name is main
default package name is main
In main
In PKG1
I'm PKG0
I'm PKG1
I'm main
I'm main 
I'm main
__PACKAGE__ で、その位置のパッケージ名を参照できます。
トップレベルのパッケージ名は、main です。
package PKG0 は、をブロックを伴って宣言されているので、ブロックを抜けると main パッケージに戻ります。
package PKG1 は、をブロックを伴わず宣言されているので、次の package 宣言までが PKG0 パッケージです。
&PKG0::fで、PKG0パッケージのfが、 &PKG1::fで、PKG1パッケージのfが。
&main::f&::fあるいは&fで、mainパッケージのfが参照されます。

ourで宣言された変数は、パッケージ変数です。パッケージ変数はグローバル変数ですが、パッケージに属しています。 our宣言の場所のスコープでしか単純な名前での参照はできませんが、::をつかった完全修飾形式であれば、ourのスコープの外からも参照できます。

our 宣言を含むコード
use v5.20.0;

our $x = "default package name is @{[ __PACKAGE__ ]}";

package PKG0 {
  our $x = "I'm @{[ __PACKAGE__ ]}"
}

package PKG1;
our $x = "I'm @{[ __PACKAGE__ ]}";

package main;

print <<EOS;
$\PKG0::x --> $PKG0::x
$\PKG1::x --> $PKG1::x
$\main::x --> $main::x
$\::x --> $::x
$\x --> $x
EOS
実行結果
PKG0::x --> I'm PKG0
PKG1::x --> I'm PKG1
main::x --> default package name is main
::x --> default package name is main 
x --> I'm PKG1
最後だけ意外ですが、PKG1 の our $x のレキシカルスコープは尽きていないので、main::x を押し置けて PKG1::x が参照されます。

特殊コードブロック

編集

Perlも、AWK の BEGIN, END のように特定のタイミングで実行されるコードブロックを定義できます。 特殊コードブロックは、サブルーチンと外観は似ていますが、同じパッケージに2つ以上定義することもできます。まや、直接呼出すことはできません。 5つのどのコードブロックで実行されているかは、${^GLOBAL_PHASE} で参照できます。

BEGINコードブロックは、パースした端から実行されます。 AWKBEGINと同様です。

UNITCHECK

編集

UNITCHECKブロックは、それを定義したユニットがコンパイルされた直後に実行されます。 メインプログラムファイルとそれがロードする各モジュールはコンパイル単位であり、文字列評価、正規表現内の (?{ }) 構成を使用してコンパイルされたランタイムコード、do FILE、require FILEの呼び出し、コマンドライン上の-eスイッチの後のコードも同様です。

CHECK コードブロックは、最初の Perl コンパイルフェーズ終了直後、 実行時が開始する直前に、LIFO 順で実行されます。 CHECK コードブロックは Perl コンパイラスイートがプログラムのコンパイル 状態を保存するために使われます。

INIT ブロックは Perl ランタイムが実行を開始する直前に、「先入れ先出し」 (FIFO) 順で実行されます。

ENDコードブロックはできるだけ遅く、perlがプログラムを実行し終わった後、インタープリターが終了する直前に実行されます。

たとえ、die関数の結果として終了する場合でも同様です。
しかし、execによって他のプログラムに遷移した場合は実行されません。
さらに、ハンドリングされていないシグナルによるアボートの場合も実行されません。
(可能であれば)自分でトラップしなければなりません。
1つのファイルに複数のENDブロックがあっても、それらは定義の逆順で実行されます。
つまり、LIFO(Last In, First Out)です。
ENDブロックは、perlを-cスイッチ付きで実行したときや、コンパイルに失敗したときには実行されません。

AWKENDと同様です。

モジュール

編集
構文
use モジュール名 [ 識別子 ];

プラグマ

編集

プラグマは、Perl のコンパイル時や実行時の動作に影響を与えるモジュールです。 strict や warnings のように、Perl のコンパイル時や実行時の動作に影響を与えるモジュールです。 Perl 5.10 からは、ユーザーもプラグマを定義できるようになりました。

strict

編集

strictプラグマを有効にすると、宣言済みでないグローバル変数やシンボリックリファレンスなど危険なものの使用を禁止します。それらが出現した時点で例外を発生させ、プログラムを終了します。

use v5.12 以降は strict が[6]ディフォルトで有効です。

use strict;

use モジュール名;とすると、モジュールを使用することができます。対義語はno モジュール名;で、モジュールを不使用にします。

use strict;
{
    no strict 'refs'; # このブロックの中ではシンボリックリファレンスを使用可能にする
}

strictプラグマはレキシカルスコープを持つので、このようにブロック内でのみ無効にするということができます。

$a と $b
$a と $b は、sort() を使うときの特別なパッケージ変数です。

この特殊性のため、$a と $b は "strict 'vars'" プラグマを使用しても、"use vars" や "our()" を使って宣言する必要はありません。 sort() 比較ブロックや関数で使用したい場合は、「my $a」や「my $b」でレキシカルスコープにしないようにしましょう。

Perlのプログラミングの教本で、変数の例に $a や $b を使っている場合、筆者は特別なパッケージ変数であることに思い至っていないことになります。

warnings

編集
use warnings;

で、警告の機能を追加できます。

これはperlの -w スイッチと同じで、無意味な演算や未定義の変数の使用、一度も使用されていない変数などに対する警告を有効にします。

use v5.36 以降は、warnings がディフォルトで有効です[7]

警告するだけで、プログラムは続行されます。

ワンライナーや書き捨てのスクリプトを作成する時以外は、strictプラグマと共に常に有効にすることが推奨されます。

標準モジュール

編集

perlに標準で同梱されているモジュールのことを標準モジュールといいます。標準モジュール以外のライブラリは、CPANなどから入手します。

標準モジュールの一覧とサポートバージョンの一覧を表示するコード
use v5.30.0;
use warnings;
use Module::CoreList;

my $version = '5.030000';
my $modules = $Module::CoreList::version{$version};
print <<EOS;
Modules in perl $version:
@{[ join "\n",  (sort keys %$modules) ]}

version in Module::CoreList::version:
@{[ join "\n",  (sort keys %Module::CoreList::version) ]}
EOS
実行結果
Modules in perl 5.030000:
Amiga::ARexx
Amiga::Exec
AnyDBM_File
App::Cpan
App::Prove
App::Prove::State
App::Prove::State::Result
App::Prove::State::Result::Test
Archive::Tar
Archive::Tar::Constant
Archive::Tar::File
Attribute::Handlers
AutoLoader
AutoSplit
B
B::Concise
B::Deparse
B::Op_private
B::Showlex
B::Terse
B::Xref
Benchmark
CPAN
CPAN::Author
CPAN::Bundle
CPAN::CacheMgr
CPAN::Complete
CPAN::Debug
CPAN::DeferredCode
CPAN::Distribution
CPAN::Distroprefs
CPAN::Distrostatus
CPAN::Exception::RecursiveDependency
CPAN::Exception::blocked_urllist
CPAN::Exception::yaml_not_installed
CPAN::Exception::yaml_process_error
CPAN::FTP
CPAN::FTP::netrc
CPAN::FirstTime
CPAN::HTTP::Client
CPAN::HTTP::Credentials
CPAN::HandleConfig
CPAN::Index
CPAN::InfoObj
CPAN::Kwalify
CPAN::LWP::UserAgent
CPAN::Meta
CPAN::Meta::Converter
CPAN::Meta::Feature
CPAN::Meta::History
CPAN::Meta::Merge
CPAN::Meta::Prereqs
CPAN::Meta::Requirements
CPAN::Meta::Spec
CPAN::Meta::Validator
CPAN::Meta::YAML
CPAN::Mirrors
CPAN::Module
CPAN::Nox
CPAN::Plugin
CPAN::Plugin::Specfile
CPAN::Prompt
CPAN::Queue
CPAN::Shell
CPAN::Tarzip
CPAN::URL
CPAN::Version
Carp
Carp::Heavy
Class::Struct
Compress::Raw::Bzip2
Compress::Raw::Zlib
Compress::Zlib
Config
Config::Extensions
Config::Perl::V
Cwd
DB
DBM_Filter
DBM_Filter::compress
DBM_Filter::encode
DBM_Filter::int32
DBM_Filter::null
DBM_Filter::utf8
DB_File
Data::Dumper
Devel::PPPort
Devel::Peek
Devel::SelfStubber
Digest
Digest::MD5
Digest::SHA
Digest::base
Digest::file
DirHandle
Dumpvalue
DynaLoader
Encode
Encode::Alias
Encode::Byte
Encode::CJKConstants
Encode::CN
Encode::CN::HZ
Encode::Config
Encode::EBCDIC
Encode::Encoder
Encode::Encoding
Encode::GSM0338
Encode::Guess
Encode::JP
Encode::JP::H2Z
Encode::JP::JIS7
Encode::KR
Encode::KR::2022_KR
Encode::MIME::Header
Encode::MIME::Header::ISO_2022_JP
Encode::MIME::Name
Encode::Symbol
Encode::TW
Encode::Unicode
Encode::Unicode::UTF7
English
Env
Errno
Exporter
Exporter::Heavy
ExtUtils::CBuilder
ExtUtils::CBuilder::Base
ExtUtils::CBuilder::Platform::Unix
ExtUtils::CBuilder::Platform::VMS
ExtUtils::CBuilder::Platform::Windows
ExtUtils::CBuilder::Platform::Windows::BCC
ExtUtils::CBuilder::Platform::Windows::GCC
ExtUtils::CBuilder::Platform::Windows::MSVC
ExtUtils::CBuilder::Platform::aix
ExtUtils::CBuilder::Platform::android
ExtUtils::CBuilder::Platform::cygwin
ExtUtils::CBuilder::Platform::darwin
ExtUtils::CBuilder::Platform::dec_osf
ExtUtils::CBuilder::Platform::os2
ExtUtils::Command
ExtUtils::Command::MM
ExtUtils::Constant
ExtUtils::Constant::Base
ExtUtils::Constant::ProxySubs
ExtUtils::Constant::Utils
ExtUtils::Constant::XS
ExtUtils::Embed
ExtUtils::Install
ExtUtils::Installed
ExtUtils::Liblist
ExtUtils::Liblist::Kid
ExtUtils::MM
ExtUtils::MM_AIX
ExtUtils::MM_Any
ExtUtils::MM_BeOS
ExtUtils::MM_Cygwin
ExtUtils::MM_DOS
ExtUtils::MM_Darwin
ExtUtils::MM_MacOS
ExtUtils::MM_NW5
ExtUtils::MM_OS2
ExtUtils::MM_QNX
ExtUtils::MM_UWIN
ExtUtils::MM_Unix
ExtUtils::MM_VMS
ExtUtils::MM_VOS
ExtUtils::MM_Win32
ExtUtils::MM_Win95
ExtUtils::MY
ExtUtils::MakeMaker
ExtUtils::MakeMaker::Config
ExtUtils::MakeMaker::Locale
ExtUtils::MakeMaker::version
ExtUtils::MakeMaker::version::regex
ExtUtils::Manifest
ExtUtils::Miniperl
ExtUtils::Mkbootstrap
ExtUtils::Mksymlists
ExtUtils::Packlist
ExtUtils::ParseXS
ExtUtils::ParseXS::Constants
ExtUtils::ParseXS::CountLines
ExtUtils::ParseXS::Eval
ExtUtils::ParseXS::Utilities
ExtUtils::Typemaps
ExtUtils::Typemaps::Cmd
ExtUtils::Typemaps::InputMap
ExtUtils::Typemaps::OutputMap
ExtUtils::Typemaps::Type
ExtUtils::XSSymSet
ExtUtils::testlib
Fatal
Fcntl
File::Basename
File::Compare
File::Copy
File::DosGlob
File::Fetch
File::Find
File::Glob
File::GlobMapper
File::Path
File::Spec
File::Spec::AmigaOS
File::Spec::Cygwin
File::Spec::Epoc
File::Spec::Functions
File::Spec::Mac
File::Spec::OS2
File::Spec::Unix
File::Spec::VMS
File::Spec::Win32
File::Temp
File::stat
FileCache
FileHandle
Filter::Simple
Filter::Util::Call
FindBin
GDBM_File
Getopt::Long
Getopt::Std
HTTP::Tiny
Hash::Util
Hash::Util::FieldHash
I18N::Collate
I18N::LangTags
I18N::LangTags::Detect
I18N::LangTags::List
I18N::Langinfo
IO
IO::Compress::Adapter::Bzip2
IO::Compress::Adapter::Deflate
IO::Compress::Adapter::Identity
IO::Compress::Base
IO::Compress::Base::Common
IO::Compress::Bzip2
IO::Compress::Deflate
IO::Compress::Gzip
IO::Compress::Gzip::Constants
IO::Compress::RawDeflate
IO::Compress::Zip
IO::Compress::Zip::Constants
IO::Compress::Zlib::Constants
IO::Compress::Zlib::Extra
IO::Dir
IO::File
IO::Handle
IO::Pipe
IO::Poll
IO::Seekable
IO::Select
IO::Socket
IO::Socket::INET
IO::Socket::IP
IO::Socket::UNIX
IO::Uncompress::Adapter::Bunzip2
IO::Uncompress::Adapter::Identity
IO::Uncompress::Adapter::Inflate
IO::Uncompress::AnyInflate
IO::Uncompress::AnyUncompress
IO::Uncompress::Base
IO::Uncompress::Bunzip2
IO::Uncompress::Gunzip
IO::Uncompress::Inflate
IO::Uncompress::RawInflate
IO::Uncompress::Unzip
IO::Zlib
IPC::Cmd
IPC::Msg
IPC::Open2
IPC::Open3
IPC::Semaphore
IPC::SharedMem
IPC::SysV
JSON::PP
JSON::PP::Boolean
List::Util
List::Util::XS
Locale::Maketext
Locale::Maketext::Guts
Locale::Maketext::GutsLoader
Locale::Maketext::Simple
MIME::Base64
MIME::QuotedPrint
Math::BigFloat
Math::BigFloat::Trace
Math::BigInt
Math::BigInt::Calc
Math::BigInt::FastCalc
Math::BigInt::Lib
Math::BigInt::Trace
Math::BigRat
Math::Complex
Math::Trig
Memoize
Memoize::AnyDBM_File
Memoize::Expire
Memoize::ExpireFile
Memoize::ExpireTest
Memoize::NDBM_File
Memoize::SDBM_File
Memoize::Storable
Module::CoreList
Module::CoreList::Utils
Module::Load
Module::Load::Conditional
Module::Loaded
Module::Metadata
Moped::Msg
NDBM_File
NEXT
Net::Cmd
Net::Config
Net::Domain
Net::FTP
Net::FTP::A
Net::FTP::E
Net::FTP::I
Net::FTP::L
Net::FTP::dataconn
Net::NNTP
Net::Netrc
Net::POP3
Net::Ping
Net::SMTP
Net::Time
Net::hostent
Net::netent
Net::protoent
Net::servent
O
ODBM_File
OS2::DLL
OS2::ExtAttr
OS2::PrfDB
OS2::Process
OS2::REXX
Opcode
POSIX
Params::Check
Parse::CPAN::Meta
Perl::OSType
PerlIO
PerlIO::encoding
PerlIO::mmap
PerlIO::scalar
PerlIO::via
PerlIO::via::QuotedPrint
Pod::Checker
Pod::Escapes
Pod::Find
Pod::Functions
Pod::Functions::Functions
Pod::Html
Pod::InputObjects
Pod::Man
Pod::ParseLink
Pod::ParseUtils
Pod::Parser
Pod::Perldoc
Pod::Perldoc::BaseTo
Pod::Perldoc::GetOptsOO
Pod::Perldoc::ToANSI
Pod::Perldoc::ToChecker
Pod::Perldoc::ToMan
Pod::Perldoc::ToNroff
Pod::Perldoc::ToPod
Pod::Perldoc::ToRtf
Pod::Perldoc::ToTerm
Pod::Perldoc::ToText
Pod::Perldoc::ToTk
Pod::Perldoc::ToXml
Pod::PlainText
Pod::Select
Pod::Simple
Pod::Simple::BlackBox
Pod::Simple::Checker
Pod::Simple::Debug
Pod::Simple::DumpAsText
Pod::Simple::DumpAsXML
Pod::Simple::HTML
Pod::Simple::HTMLBatch
Pod::Simple::HTMLLegacy
Pod::Simple::LinkSection
Pod::Simple::Methody
Pod::Simple::Progress
Pod::Simple::PullParser
Pod::Simple::PullParserEndToken
Pod::Simple::PullParserStartToken
Pod::Simple::PullParserTextToken
Pod::Simple::PullParserToken
Pod::Simple::RTF
Pod::Simple::Search
Pod::Simple::SimpleTree
Pod::Simple::Text
Pod::Simple::TextContent
Pod::Simple::TiedOutFH
Pod::Simple::Transcode
Pod::Simple::TranscodeDumb
Pod::Simple::TranscodeSmart
Pod::Simple::XHTML
Pod::Simple::XMLOutStream
Pod::Text
Pod::Text::Color
Pod::Text::Overstrike
Pod::Text::Termcap
Pod::Usage
SDBM_File
Safe
Scalar::Util
Search::Dict
SelectSaver
SelfLoader
Socket
Storable
Sub::Util
Symbol
Sys::Hostname
Sys::Syslog
Sys::Syslog::Win32
TAP::Base
TAP::Formatter::Base
TAP::Formatter::Color
TAP::Formatter::Console
TAP::Formatter::Console::ParallelSession
TAP::Formatter::Console::Session
TAP::Formatter::File
TAP::Formatter::File::Session
TAP::Formatter::Session
TAP::Harness
TAP::Harness::Env
TAP::Object
TAP::Parser
TAP::Parser::Aggregator
TAP::Parser::Grammar
TAP::Parser::Iterator
TAP::Parser::Iterator::Array
TAP::Parser::Iterator::Process
TAP::Parser::Iterator::Stream
TAP::Parser::IteratorFactory
TAP::Parser::Multiplexer
TAP::Parser::Result
TAP::Parser::Result::Bailout
TAP::Parser::Result::Comment
TAP::Parser::Result::Plan
TAP::Parser::Result::Pragma
TAP::Parser::Result::Test
TAP::Parser::Result::Unknown
TAP::Parser::Result::Version
TAP::Parser::Result::YAML
TAP::Parser::ResultFactory
TAP::Parser::Scheduler
TAP::Parser::Scheduler::Job
TAP::Parser::Scheduler::Spinner
TAP::Parser::Source
TAP::Parser::SourceHandler
TAP::Parser::SourceHandler::Executable
TAP::Parser::SourceHandler::File
TAP::Parser::SourceHandler::Handle
TAP::Parser::SourceHandler::Perl
TAP::Parser::SourceHandler::RawTAP
TAP::Parser::YAMLish::Reader
TAP::Parser::YAMLish::Writer
Term::ANSIColor
Term::Cap
Term::Complete
Term::ReadLine
Test
Test2
Test2::API
Test2::API::Breakage
Test2::API::Context
Test2::API::Instance
Test2::API::Stack
Test2::Event
Test2::Event::Bail
Test2::Event::Diag
Test2::Event::Encoding
Test2::Event::Exception
Test2::Event::Fail
Test2::Event::Generic
Test2::Event::Note
Test2::Event::Ok
Test2::Event::Pass
Test2::Event::Plan
Test2::Event::Skip
Test2::Event::Subtest
Test2::Event::TAP::Version
Test2::Event::V2
Test2::Event::Waiting
Test2::EventFacet
Test2::EventFacet::About
Test2::EventFacet::Amnesty
Test2::EventFacet::Assert
Test2::EventFacet::Control
Test2::EventFacet::Error
Test2::EventFacet::Hub
Test2::EventFacet::Info
Test2::EventFacet::Info::Table
Test2::EventFacet::Meta
Test2::EventFacet::Parent
Test2::EventFacet::Plan
Test2::EventFacet::Render
Test2::EventFacet::Trace
Test2::Formatter
Test2::Formatter::TAP
Test2::Hub
Test2::Hub::Interceptor
Test2::Hub::Interceptor::Terminator
Test2::Hub::Subtest
Test2::IPC
Test2::IPC::Driver
Test2::IPC::Driver::Files
Test2::Tools::Tiny
Test2::Util
Test2::Util::ExternalMeta
Test2::Util::Facets2Legacy
Test2::Util::HashBase
Test2::Util::Trace
Test::Builder
Test::Builder::Formatter
Test::Builder::IO::Scalar
Test::Builder::Module
Test::Builder::Tester
Test::Builder::Tester::Color
Test::Builder::TodoDiag
Test::Harness
Test::More
Test::Simple
Test::Tester
Test::Tester::Capture
Test::Tester::CaptureRunner
Test::Tester::Delegate
Test::use::ok
Text::Abbrev
Text::Balanced
Text::ParseWords
Text::Tabs
Text::Wrap
Thread
Thread::Queue
Thread::Semaphore
Tie::Array
Tie::File
Tie::Handle
Tie::Hash
Tie::Hash::NamedCapture
Tie::Memoize
Tie::RefHash
Tie::Scalar
Tie::StdHandle
Tie::SubstrHash
Time::HiRes
Time::Local
Time::Piece
Time::Seconds
Time::gmtime
Time::localtime
Time::tm
UNIVERSAL
Unicode
Unicode::Collate
Unicode::Collate::CJK::Big5
Unicode::Collate::CJK::GB2312
Unicode::Collate::CJK::JISX0208
Unicode::Collate::CJK::Korean
Unicode::Collate::CJK::Pinyin
Unicode::Collate::CJK::Stroke
Unicode::Collate::CJK::Zhuyin
Unicode::Collate::Locale
Unicode::Normalize
Unicode::UCD
User::grent
User::pwent
VMS::DCLsym
VMS::Filespec
VMS::Stdio
Win32
Win32API::File
Win32API::File::inc::ExtUtils::Myconst2perl
Win32CORE
XS::APItest
XS::Typemap
XSLoader
_charnames
attributes
autodie
autodie::Scope::Guard
autodie::Scope::GuardStack
autodie::Util
autodie::exception
autodie::exception::system
autodie::hints
autodie::skip
autouse
base
bigint
bignum
bigrat
blib
bytes
charnames
constant
deprecate
diagnostics
encoding
encoding::warnings
experimental
feature
fields
filetest
if
integer
less
lib
locale
meta_notation
mro
ok
open
ops
overload
overload::numbers
overloading
parent
perlfaq
re
sigtrap
sort
strict
subs
threads
threads::shared
unicore::Name
utf8
vars
version
version::regex
vmsish
warnings
warnings::register

version in Module::CoreList::version:
5
5.000
5.001
5.002
5.00307
5.004
5.00405
5.005
5.00503
5.00504
5.006
5.006000
5.006001
5.006002
5.007003
5.008
5.008000
5.008001
5.008002
5.008003
5.008004
5.008005
5.008006
5.008007
5.008008
5.008009
5.009
5.009000
5.009001
5.009002
5.009003
5.009004
5.009005
5.01
5.010000
5.010001
5.011
5.011000
5.011001
5.011002
5.011003
5.011004
5.011005
5.012
5.012000
5.012001
5.012002
5.012003
5.012004
5.012005
5.013
5.013000
5.013001
5.013002
5.013003
5.013004
5.013005
5.013006
5.013007
5.013008
5.013009
5.01301
5.013010
5.013011
5.014
5.014000
5.014001
5.014002
5.014003
5.014004
5.015
5.015000
5.015001
5.015002
5.015003
5.015004
5.015005
5.015006
5.015007
5.015008
5.015009
5.016
5.016000
5.016001
5.016002
5.016003
5.017
5.017000
5.017001
5.017002
5.017003
5.017004
5.017005
5.017006
5.017007
5.017008
5.017009
5.01701
5.017010
5.017011
5.018
5.018000
5.018001
5.018002
5.018003
5.018004
5.019
5.019000
5.019001
5.019002
5.019003
5.019004
5.019005
5.019006
5.019007
5.019008
5.019009
5.01901
5.019010
5.019011
5.02
5.020000
5.020001
5.020002
5.020003
5.021
5.021000
5.021001
5.021002
5.021003
5.021004
5.021005
5.021006
5.021007
5.021008
5.021009
5.02101
5.021010
5.021011
5.022
5.022000
5.022001
5.022002
5.022003
5.022004
5.023
5.023000
5.023001
5.023002
5.023003
5.023004
5.023005
5.023006
5.023007
5.023008
5.023009
5.024
5.024000
5.024001
5.024002
5.024003
5.024004
5.025
5.025000
5.025001
5.025002
5.025003
5.025004
5.025005
5.025006
5.025007
5.025008
5.025009
5.02501
5.025010
5.025011
5.025012
5.026
5.026000
5.026001
5.026002
5.026003
5.027
5.027000
5.027001
5.027002
5.027003
5.027004
5.027005
5.027006
5.027007
5.027008
5.027009
5.02701
5.027010
5.027011
5.028
5.028000
5.028001
5.028002
5.029
5.029000
5.029001
5.029002
5.029003
5.029004
5.029005
5.029006
5.029007
5.029008
5.029009
5.02901
5.029010
5.03
5.030000

CPAN (Comprehensive Perl Archive Network) とは、Perlのライブラリ、モジュール、その他のスクリプトなどを集めた世界的なアーカイブネットワークです。標準モジュールのCPAN.pmでは、シェルからcpanコマンドを使ってCPANのモジュールをインストールするインタフェースを提供しています。

モジュールの作成

編集

非オブジェクト指向版

編集
lib/Category/Example.pm
package Category::Example {
    use v5.30.0;

    BEGIN {
        require Exporter;

        # バージョンチェックのためのバージョン
        our $VERSION     = 1.00;

        # Exporterを継承して関数と変数をエクスポートする
        our @ISA         = qw(Exporter);

        # デフォルトでエクスポートされる関数と変数
        our @EXPORT      = qw(func1 func2);

        # オプションでエクスポート可能な関数と変数
        our @EXPORT_OK   = qw($Var1 %Hashit func3);
    }

    # エクスポートされるパッケージのグローバル識別子
    our $Var1    = '';
    our %Hashit  = ();

    # エクスポートされないパッケージのグローバル識別子
    # (これらは、$Category::Example::stuffとしてまだアクセス可能です)
    our @more    = ();
    our $stuff   = 'stuff';

    # ファイルプライベートレキシカルは、それらを使用する関数の前に、ここに置かれます。
    my $priv_var    = '';
    my %secret_hash = ();

    # ここでは、ファイル・プライベート関数をクロージャとして、
    # $priv_func->() として呼び出しています。
    my $priv_func = sub {
        ...
    };

    # エクスポートされている関数の実装。
    sub func1      { return "func1" }
    sub func2      { return "func2" }

    # これはエクスポートされませんが、
    # Some::Module::func3() として直接呼び出すことができます。
    sub func3      { return "func3" }

    END {     }       # モジュールのクリーンアップコード(グローバルデストラクター)。
}
1;  # true を返すことを忘れないでください。
Main.pl
use v5.30.0;
use lib './lib';

use Category::Example;

say func1;
say func2;
say Category::Example::func3;
オブジェクト指向でないモジュール実装の例です。
モジュールの拡張子は .pm (Perl Modules)で、モジュール階層の区切り :: をファイルシステムのディレクトセパレーターに置き換えたものがパスになります。: モジュールは package として実装します。
コンパイル単位を超えて識別子をエキスポートするには Exporter モジュールを使います。

オブジェクト指向版

編集
lib/Point.pm
package Point {
    use v5.30.0;
    use feature 'signatures';
    no warnings "experimental::signatures";
    use POSIX qw[hypot];
    
    BEGIN {
        our @VERSION = "1.2.0";
    }

    sub new ( $class, $x = 0.0, $y = 0.0 ) {
        bless { x => $x, y => $y, }, $class;
    }
    use overload
        '""'  => sub ( $self, $p, $q ) {"Point($self->{x}, $self->{y})"},
        'abs' => sub ( $self, $p, $q ) { POSIX::hypot( $self->{x}, $self->{y} ) };
    sub abs   ($self) { POSIX::hypot( $self->{x}, $self->{y} ) }
    sub angle ($self) { atan2( $self->{x}, $self->{y} ) }
}

if ( $0 eq __FILE__ ) {

    my $pt = Point->new( 6.0, 8.0 );

    print <<EOS;
\@Point::VERSION: @{[ @Point::VERSION ]}
\$pt: $pt 
\$pt->abs(): @{[ $pt->abs() ]}
\$pt->angle(): @{[ $pt->angle() ]}
EOS
}
1;
Main.pl
use v5.30.0;
use lib q(./lib);
use Point;

my $pt = Point->new( 3.0, 4.0 );

print <<EOS;
\@Point::VERSION: @{[ @Point::VERSION ]}
\$pt: $pt 
abs \$pt: @{[ abs $pt ]}
\$pt->abs: @{[ $pt->abs ]}
\$pt->angle(): @{[ $pt->angle() ]}
EOS
実行結果
@Point::VERSION: 1.2.0
$pt: Point(3, 4) 
abs $pt: 5
$pt->abs: 5 
$pt->angle(): 0.643501108793284
オブジェクト指向のモジュール実装の例です。
abs は、単項演算子でもあるのでメソッド版と演算子版の2つを用意しました。
package をクラスとして使っているので、Exporter の出番はなく、完全修飾形式が基本になります。
呼出し元のパッケージ(典型的には main::)の名前空間を汚染しないのがよいです。
use overload '""' => sub($self, $p, $q) { "Point($self->{x}, $self->{y})" };は、文字列化演算子を演算子オーバーロードしています。

Perlとオブジェクト指向

編集

Perl のオブジェクト指向の特徴

編集
  1. クラスベースのオブジェクト指向
  2. クラスは、package 構文の拡張
  3. コンストラクターの中核は bless 関数
  4. @ISA による継承機構
    1. 単純継承だけでなく多重継承をサポート
  5. overload モジュールを使うことで演算子オーバーロードが可能

具体的な実装例 

編集
直交座標系の1点を表すクラス Point
use v5.30.0;
use feature 'signatures';
no warnings "experimental::signatures";
use POSIX ();

package Point {

    BEGIN {
        our @VERSION = '1.2.0';
    }

    sub new : prototype($$$) ( $class, $x = 0.0, $y = 0.0 ) {
        bless { x => $x, y => $y, }, $class;
    }

    use overload
        '""'  => sub ( $self, $p, $q ) {"Point($self->{x}, $self->{y})"},
        'abs' => sub ( $self, $p, $q ) { POSIX::hypot @$self{qw(x y)} };
    sub abs : prototype($) ($self) { POSIX::hypot @$self{qw(x y)} }
    sub angle ($self) { atan2 $self->{x}, $self->{y} }
}

package main {
    my $pt = Point->new( 3.0, 4.0 );

    print <<EOS;
\@Point::VERSION: @Point::VERSION
\$pt: $pt
abs \$pt: @{[ abs $pt ]}
\$pt->abs(): @{[ $pt->abs() ]}
\$pt->angle(): @{[ $pt->angle() ]}
\$pt->{x}: @{[ $pt->{x} ]}
\$pt->{y}: @{[ $pt->{y} ]}
\@\$pt{qw(x y)}: @{[ @$pt{qw(x y)} ]}
EOS
}
実行結果
@Point::VERSION: 1.2.0
$pt: Point(3, 4)
abs $pt: 5
$pt->abs(): 5
$pt->angle(): 0.643501108793284
$pt->{x}: 3
$pt->{y}: 4 
@$pt{qw(x y)}: 3 4

コンストラクター

編集

コンストラクターはオブジェクトを返すサブルーチンです。他の多くの言語と同じく名前には new を使います。 他の名前でも、データ構造をクラスに bless し返すサブルーチンは全てコンストラクターです。

コンストラクターの定義
    sub new : prototype($$$) ( $class, $x = 0.0, $y = 0.0 ) {
        bless { x => $x, y => $y, }, $class;
    }
use feature 'signatures';しているのでモダンでスタイリッシュですが
非シグネチャーでコンストラクターの定義
    sub new {
        my $class = shift;
        bless { x => shift // 0.0, y => shift // 0.0, }, $class;
    }
とも書けます。
コンストラクターの呼出し
  my $pt = Point->new(3.0, 4.0);
Point が隠れた第一引数として渡されます。
間接オブジェクト文法
  my $pt = new Point(3.0, 4.0);
これは、間接オブジェクト文法( indirect object notation )という構文ですが、v5.36で廃止されました

組込み関数blessは、コンストラクターの中核で、第一引数(典型的には $self という名前のハッシュ)と、第二引数の(典型的には $class と言う名前のパッケージ)を結びつけたインスタンス(クラスを実体化したオブジェクト)を戻値とします。bless の戻値を使ってメソッドメンバーを参照します。

オブジェクトの内部構造 $self は、典型的にはハッシュが使われますが、これはハッシュはキー(名前)によって値を取り出すことができるためメンバーを表現するのに適しているためです。 ほかのデータ構造、配列・スカラー・ファイルハンドルなどを内部構造にすることもあります。

クラス

編集

クラスの宣言はpackage宣言によって行います。これはライブラリ・モジュールがパッケージを宣言するのと文法的には全く同じです。

メソッド

編集

メソッドの定義は関数定義と同じsubによって行われます。メソッドは第一引数にオブジェクト(慣習として $self の名前が使われます)が渡されるサブルーチンです。

$pt->abs()
のようにしてアクセスされるメソッドは、
シグネチャー版
  sub abs($self) { POSIX::hypot($self->{x}, $self->{y}) }
のように定義されます。
非シグネチャー版
  sub abs {
    my $self = shift;
    POSIX::hypot($self->{x}, $self->{y})
  }

メンバー

編集

bless でパッケージと結ぶ付けられたデータ構造にハッシュを使った場合、キーを名前とするメンバー変数として振舞います。

$pt->{x}
$pt->{y}

のようにリファレンスで参照します。

クラス変数

編集

Perlでは、パッケージ変数がクラス変数に相当します。

$Point::VERSION

のように、パッケージ内でour宣言された変数(パッケージ変数)はクラス変数として振舞います。

デストラクター

編集

オブジェクトへの最後の参照がなくなると、そのオブジェクトは破棄されます。

  • レキシカルスカラー変数(1つだけ)にオブジェクトへの参照が束縛されている場合、その変数がスコープを出たときにオブジェクトが破棄されます。
  • パッケージグローバル変数にオブジェクトへの参照が束縛されている場合、(その変数に別の値が入りでもしな限り)プログラム終了までオブジェクトは破棄されません。

このオブジェクトが「破棄」されるサブルーチンがデストラクターです。

DESTROY

編集

デストラクターは、DESTROY と言う名前です(new と異なり名前は DESTROY 固定です)。

DESTROY メソッドはいつでも呼び出すことができるので、 DESTROY メソッドで行う何かによって設定されるかもしれないグローバルなステータス変数をローカル化しなければいけません。 このため、DESTROYのプロローグは下のようなものになります。

sub DESTROY($self) {
    local($., $@, $!, $^E, $?);

    ...;
}

継承

編集

オブジェクト指向プログラミングでは、既存のクラスから性質の部分的に異なるクラスを派生させることを継承といいます。

基底クラス
ベースクラス
派生元のクラス
派生クラス
デライブドクラス
派生先のクラス
$pt->abs();

としたとき、Perlは$pt属するクラス(=パッケージ)にabsという名前のメソッドを探しにいきます。 もし見つからなかった場合は、@ISAという特殊な配列に格納されているクラスにabsという名前のメソッドを探しにいきます。

@ISAに基底クラスの名前を入れておくことで、継承を実現することができます。

単一継承
編集

@ISA の要素数が1の継承は単一継承です。

単一継承
use v5.30.0;
use warnings;

package BaseClass {
    sub new     { bless {}, shift }
    sub hello   { say "hello I'm @{[ __PACKAGE__ ]}" }
    sub goodbye { say "goodbye I'm @{[ __PACKAGE__ ]}" }
}

package MyClass {

    BEGIN {
        our @ISA = qw(BaseClass);
    }

    sub new {
        my $class = shift;
        my $self  = $class->SUPER::new(@_);
        $self;
    }
    sub goodbye { say "goodbye I'm @{[ __PACKAGE__ ]}" }
}

my $mc = MyClass->new();
say qq(@{[ $mc->isa("MyClass") ? "t" : "()"]});
say qq(@{[ $mc->isa("BaseClass") ? "t" : "()" ]});
say qq(@{[ $mc->isa("OtherClass") ? "t" : "()" ]});
$mc->hello();
$mc->goodbye();
実行結果
t
t
()
hello I'm BaseClass 
goodbye I'm MyClass
多重継承
編集

@ISAに複数のクラス名を列挙する継承が多重継承です。

多重継承
use v5.30.0;
use warnings;

package BaseClass1 {
    sub new     { bless {}, shift }
    sub hello   { say "hello I'm @{[ __PACKAGE__ ]}" }
    sub goodbye { say "goodbye I'm @{[ __PACKAGE__ ]}" }
}

package BaseClass2 {
    sub new     { bless {}, shift }
    sub hello   { say "hello I'm @{[ __PACKAGE__ ]}" }
    sub goodbye { say "goodbye I'm @{[ __PACKAGE__ ]}" }
}

package MyClass {

    BEGIN {
        our @ISA = qw(BaseClass1 BaseClass2);
    }

    sub new {
        my $class = shift;
        my $self  = $class->SUPER::new(@_);
        $self;
    }
}

my $mc = MyClass->new();
say qq(@{[ $mc->isa("MyClass") ? "t" : "()"]});
say qq(@{[ $mc->isa("BaseClass1") ? "t" : "()" ]});
say qq(@{[ $mc->isa("BaseClass2") ? "t" : "()" ]});
say qq(@{[ $mc->isa("OtherClass") ? "t" : "()" ]});
$mc->hello();
$mc->goodbye();
実行結果
t
t
t
()
hello I'm BaseClass1 
goodbye I'm BaseClass1
ここで問題なのは、my $self = $class->SUPER::new(@_);での SUPER は BaseClass1 でもう1つの基底クラス BaseClass2 はコンストラクターが呼ばれていない点です。
このコードでは、各基底クラスのプロパティは参照されていませんが、もしプロパティを参照するとBaseClass2のメソッドが未初期化プロパティの参照を引き起こします。
幾つかの対策が考えられますが
基底クラスごとにインスタンスをプロパティの1つとして保持する ⇒ それは継承でなく包含
最初の基底クラスのnewの戻値を次の基底クラスのnewにわたすのを繰返す ⇒ blessされたオブジェクトの再blessになる
基底クラスの1つしかプロパティを持たせず、ほかはメソッドのみ ⇒ それは Mix-in
と多重継承にはメソッドの呼出の優先順以上に超えなければいけない問題があります。
ダイアモンド継承
編集

基底クラス同士が共通のクラスから派生されている継承関係をダイアモンド継承と呼びます。

ダイアモンド継承
use v5.30.0;
use warnings;

package BaseClass { }

package BaseClass1 {

    BEGIN {
        our @ISA = qw(BaseClass);
    }
}

package BaseClass2 {

    BEGIN {
        our @ISA = qw(BaseClass);
    }
}

package MyClass {

    BEGIN {
        our @ISA = qw(BaseClass1 BaseClass2);
    }
    sub new { bless {}, shift }
}

my $mc = MyClass->new();
say qq(@{[ $mc->isa("MyClass") ? "t" : "()"]});
say qq(@{[ $mc->isa("BaseClass1") ? "t" : "()" ]});
say qq(@{[ $mc->isa("BaseClass2") ? "t" : "()" ]});
say qq(@{[ $mc->isa("BaseClass") ? "t" : "()" ]});
say qq(@{[ $mc->isa("OtherClass") ? "t" : "()" ]});
実行結果
t
t
t
t 
()
Mix-in
編集

Perlの多重継承では、2つ以上のコンストラクターを呼出すスマートな方法がないので、片方はコンストラクターを用意せず、メソッドセットとして実装することとなり、実質的に Mix-in になります。

Mix-in
use v5.30.0;
use feature 'signatures';
no warnings "experimental::signatures";

package Eachable {

    BEGIN {
        our @VERSION = '1.0.0';
    }

    sub reduce ( $self, $cbr, $init = undef ) {
        my $clone = "@{[ref $self]}"->new( $self->values() );
        while ( my @pair = $clone->each() ) {
            local $_ = $pair[1];
            $init = $cbr->( $init, $_ );
        }
        return $init;
    }

    sub foreach ( $self, $cbr ) {
        my $clone = "@{[ref $self]}"->new( $self->values() );
        while ( my @pair = $clone->each() ) {
            local $_ = $pair[1];
            $cbr->(@pair);
        }
        undef;
    }

    sub map ( $self, $cbr ) {
        my @result = ();
        my $clone  = "@{[ref $self]}"->new( $self->values() );
        while ( my @pair = $clone->each() ) {
            local $_ = $pair[1];
            push @result, $cbr->(@pair);
        }
        return Array->new(@result);
    }

    sub filter ( $self, $cbr ) {
        my @result = ();
        my $clone  = "@{[ref $self]}"->new( $self->values() );
        while ( my @pair = $clone->each() ) {
            local $_ = $pair[1];
            push @result, $_ if $cbr->(@pair);
        }
        return Array->new(@result);
    }

    sub sum ( $self, $cbr = undef ) {
        my $sum   = 0;
        my $c     = 0;
        my $clone = "@{[ref $self]}"->new( $self->values() );
        while ( my @pair = $clone->each() ) {
            local $_ = $pair[0];
            my @deltas = defined $cbr ? $cbr->(@pair) : @pair[ 1 .. 1 ];
            foreach my $delta (@deltas) {
                my $y = $delta - $c;
                my $t = $sum + $y;
                $c   = ( $t - $sum ) - $y;
                $sum = $t;
            }
        }
        return $sum;
    }

    sub every ( $self, $cbr ) {
        my $clone = "@{[ref $self]}"->new( $self->values() );
        while ( my @pair = $clone->each() ) {
            local $_ = $pair[1];
            $cbr->($_) ? 0 : return 0 != 0;
        }
        return 0 == 0;
    }

    sub some ( $self, $cbr ) {
        my $clone = "@{[ref $self]}"->new( $self->values() );
        while ( my @pair = $clone->each() ) {
            local $_ = $pair[1];
            $cbr->($_) ? return 0 == 0 : 0;
        }
        return 0 != 0;
    }
}

package Array {

    BEGIN {
        our @VERSION = '1.0.0';
        our @ISA     = qw(Eachable);
    }

    sub new ( $class, @ary ) {
        bless \@ary, $class;
    }
    use overload '""' => sub ( $self, $p, $q ) {"(@{[join ',', @$self ]})"};

    sub push    ( $self, @other ) { push @$self, @other; $self }
    sub unshift ( $self, @other ) { unshift @$self, @other; $self }
    sub pop     ($self)           { pop @$self;   $self }
    sub shift   ($self)           { shift @$self; $self }
    sub keys    ($self)           { keys @$self; }
    sub values  ($self)           { values @$self; }
    sub each    ($self)           { each @$self; }

    # sub splice; XXX

}

package Hash {

    BEGIN {
        our @VERSION = '1.0.0';
        our @ISA     = qw(Eachable);
    }

    sub new ( $class, $hash ) {

        #my %self = %$hash;
        #map { $self{$_} = $hash->{$_} } keys %$hash;
        bless \%$hash, $class;
    }
    use overload '""' => sub ( $self, $p, $q ) {qq!(@{[join ',', map { "$_=>$self->{$_}" } sort keys %$self ]})!};

    # XXX
    sub delete ( $self, $key ) { delete %$self{$key} }
    sub exists ( $self, $key ) { exists $$self{$key} }
    sub keys   ($self)         { keys %$self }
    sub values ($self)         { my %clone = %$self; \%clone }
    sub each   ($self)         { each %$self }
}

if ( $0 eq __FILE__ ) {
    use Test::More tests => 35;

    say "for Array:";
    my $ary = Array->new( 1 .. 3 );
    say 'my $ary = Array->new( 1 .. 3 );';
    ok( Array->new( 1 .. 10 )->reduce( sub { my ( $x, $y ) = @_; $x + $y } ) == 55,     "Array::reduce(1)" );
    ok( Array->new( 1 .. 10 )->reduce( sub { my ( $x, $y ) = @_; $x + $y }, 10 ) == 65, "Array::reduce(2)" );
    ok( do {
            my $i;
            $ary->foreach( sub { $i += $_ } );
            $i == 6;
        },
        "Array::foreach"
    );
    ok( "" . $ary->map( sub { $_ * 2 } ) eq "(2,4,6)",                      "Array::map @{[ $ary->map(sub{$_*2}) ]}" );
    ok( "" . $ary->filter( sub { $_ % 2 == 0 } ) eq "(2)",                  "Array::filter @{[ $ary->filter( sub { $_ % 2 == 0 } ) ]}" );
    ok( "" . $ary->sum == 6,                                                "Array::sum @{[ $ary->sum ]}" );
    ok( $ary->every( sub { $_ < 10 } ),                                     'Array::every $ary->every( sub { $_ < 10 } )' );
    ok( !$ary->every( sub { $_ < 3 } ),                                     'Array::every $ary->every( sub { $_ < 3 } )' );
    ok( !$ary->every( sub { $_ == 1 } ),                                    'Array::every $ary->every( sub { $_ == 1 } )' );
    ok( !$ary->every( sub { $_ == 100 } ),                                  'Array::every $ary->every( sub { $_ == 100 } )' );
    ok( $ary->some( sub { $_ < 10 } ),                                      'Array::every $ary->some( sub { $_ < 10 } )' );
    ok( $ary->some( sub { $_ < 3 } ),                                       'Array::every $ary->some( sub { $_ < 3 } )' );
    ok( $ary->some( sub { $_ == 1 } ),                                      'Array::every $ary->some( sub { $_ == 1 } )' );
    ok( !$ary->some( sub { $_ == 100 } ),                                   'Array::every $ary->some( sub { $_ == 100 } )' );
    ok( "" . $ary eq "(1,2,3)",                                             qq(Array::Operator "" --> $ary) );
    ok( "" . $ary->push(10) eq "(1,2,3,10)",                                "Array::push --> $ary" );
    ok( "" . $ary->push( 10, 11, 12 ) eq "(1,2,3,10,10,11,12)",             "Array::push --> $ary" );
    ok( "" . $ary->pop() eq "(1,2,3,10,10,11)",                             "Array::pop --> $ary" );
    ok( "" . $ary->unshift(10) eq "(10,1,2,3,10,10,11)",                    "Array::unshift --> $ary" );
    ok( "" . $ary->unshift( 10, 11, 12 ) eq "(10,11,12,10,1,2,3,10,10,11)", "Array::unshift --> $ary" );
    ok( "" . $ary->shift() eq "(11,12,10,1,2,3,10,10,11)",                  "Array::shift --> $ary" );
    ok( "@{[$ary->keys()]}" eq "0 1 2 3 4 5 6 7 8",                         "Array::keys @{[$ary->keys()]}" );
    ok( "@{[$ary->values()]}" eq "11 12 10 1 2 3 10 10 11",                 "Array::values @{[$ary->values()]}" );

    say 'for Hash:';
    my $hash = Hash->new( { a => 2, b => 3, c => 5, d => 7 } );
    ok( "@{[sort($hash->map(sub{$_*2})->values)]}" eq "10 14 4 6",             "Hash::map @{[ sort($hash->map(sub{$_*2})->values) ]}" );
    ok( "@{[ sort $hash->filter( sub { $_ % 2 != 0 } )->values ]}" eq "3 5 7", "Hash::filter @{[ sort $hash->filter( sub { $_ % 2 != 0 } )->values ]}" );
    ok( "" . $hash->sum == 17,                                                 "Hash::sum @{[ $hash->sum ]}" );
    ok( $hash->every( sub { $_ < 10 } ),                                       'Hash::every $hash->every( sub { $_ < 10 } )' );
    ok( !$hash->every( sub { $_ < 3 } ),                                       'Hash::every $hash->every( sub { $_ < 3 } )' );
    ok( !$hash->every( sub { $_ == 1 } ),                                      'Hash::every $hash->every( sub { $_ == 1 } )' );
    ok( !$hash->every( sub { $_ == 100 } ),                                    'Hash::every $hash->every( sub { $_ == 100 } )' );
    ok( $hash->some( sub { $_ < 10 } ),                                        'Hash::every $hash->some( sub { $_ < 10 } )' );
    ok( $hash->some( sub { $_ < 3 } ),                                         'Hash::every $hash->some( sub { $_ < 3 } )' );
    ok( $hash->some( sub { $_ == 2 } ),                                        'Hash::every $hash->some( sub { $_ == 2 } )' );
    ok( !$hash->some( sub { $_ == 100 } ),                                     'Hash::every $hash->some( sub { $_ == 100 } )' );
    ok( "" . $hash eq "(a=>2,b=>3,c=>5,d=>7)",                                 qq(Hash::Operator "" --> $hash) );
}
実行結果
1..35
for Array:
my $ary = Array->new( 1 .. 3 );
ok 1 - Array::reduce(1)
ok 2 - Array::reduce(2)
ok 3 - Array::foreach
ok 4 - Array::map (2,4,6)
ok 5 - Array::filter (2)
ok 6 - Array::sum 6
ok 7 - Array::every $ary->every( sub { $_ < 10 } )
ok 8 - Array::every $ary->every( sub { $_ < 3 } )
ok 9 - Array::every $ary->every( sub { $_ == 1 } )
ok 10 - Array::every $ary->every( sub { $_ == 100 } )
ok 11 - Array::every $ary->some( sub { $_ < 10 } )
ok 12 - Array::every $ary->some( sub { $_ < 3 } )
ok 13 - Array::every $ary->some( sub { $_ == 1 } )
ok 14 - Array::every $ary->some( sub { $_ == 100 } )
ok 15 - Array::Operator "" --> (1,2,3)
ok 16 - Array::push --> (1,2,3,10)
ok 17 - Array::push --> (1,2,3,10,10,11,12)
ok 18 - Array::pop --> (1,2,3,10,10,11)
ok 19 - Array::unshift --> (10,1,2,3,10,10,11)
ok 20 - Array::unshift --> (10,11,12,10,1,2,3,10,10,11)
ok 21 - Array::shift --> (11,12,10,1,2,3,10,10,11)
ok 22 - Array::keys 0 1 2 3 4 5 6 7 8
ok 23 - Array::values 11 12 10 1 2 3 10 10 11
for Hash:
ok 24 - Hash::map 10 14 4 6
ok 25 - Hash::filter 3 5 7
ok 26 - Hash::sum 17
ok 27 - Hash::every $hash->every( sub { $_ < 10 } )
ok 28 - Hash::every $hash->every( sub { $_ < 3 } )
ok 29 - Hash::every $hash->every( sub { $_ == 1 } )
ok 30 - Hash::every $hash->every( sub { $_ == 100 } )
ok 31 - Hash::every $hash->some( sub { $_ < 10 } )
ok 32 - Hash::every $hash->some( sub { $_ < 3 } )
ok 33 - Hash::every $hash->some( sub { $_ == 2 } )
ok 34 - Hash::every $hash->some( sub { $_ == 100 } )
ok 35 - Hash::Operator "" --> (a=>2,b=>3,c=>5,d=>7)
Array と Hash は、iterator メソッドだけ実装して、each,mapやsumメソッドは、共通祖先の Eachable で実装しています。
Eachable は、コンストラクターを持たないクラスで、インスタンス化することはなく繰返しを行うメソッドだけを提供しています。
sumは、カハンの加算アルゴリズムを実装しておりアルゴリズムは、Eachableの中に閉じています。
Test::Moreモジュールによる回帰テストを用意しました。

このように、メソッドセットを合成するクラス間の関係を、Mix-inといいます。

SUPER擬似クラス( SUPER pseudo-class )は、常に基底クラスを指しています。基底クラスのメソッドを派生クラス内で呼び出す場合に使用します。

package MyClass {

    sub new {
        my $class = shift;
        my $self  = $class->SUPER::new(@_);
        return $self;
    }
}

[TODO:多事継承の場合のSUPERの振舞い]

base プラグマ

編集
base プラグマは2022年11月現在、非推奨とされ parent プラグマの使用が推奨されています。少なくとも base は多重継承に対応していません。

base プラグマを使うと、基底クラスの定義に必要なuseや@ISAの代入から基底クラス内の変数や関数のインポートまでをすべて自動で行うことができます。

package BaseClass;

package MyClass;
use base qw(BaseClass);

parent プラグマ

編集

このモジュールは、baseからフォークして、溜まっていたゴミを取り除いたものです。

package BaseClass;

package MyClass;
use base qw(BaseClass1 BaseClass2);
の様に使用しますが、これは実質的に
package BaseClass;

package MyClass;
BEGIN {
  require BaseClass1;
  require BaseClass2;
  push @ISA, qw(BaseClass1 BaseClass2)
}
と同じです(自分自身を継承しようとしているバグの検出は追加されています)。

この他にも、Class::Structの様にコンストラクターの自動生成などを行うモジュールなど、クラス定義を補助するユーティリティは幾つかありますが、手早くクラスとクラス階層の有効性を評価するのには便利ですが、クラス設計が完了した時点で、@ISAを直接操作する素朴なコードに書き換えたほうが保守性は向上します。

移植例

編集

包含と継承

編集

JavaScript/クラス#包含と継承を、Rubyに移植したコードを、OOPerl に移植しました。

包含と継承
use v5.20.0;
use feature 'signatures';
no warnings "experimental::signatures";

package Point {
  sub new($class, $x = 0, $y = 0) {
    bless { x => $x, y => $y }, $class
  }
  
  use overload '""' => sub ($self, $p, $q) { "x:$self->{x}, y:$self->{y}" };
  sub move($self, $dx = 0, $dy = 0) {
    $self->{x} += $dx;
    $self->{y} += $dy;
    $self
  }
}

package Shape {
  sub new($class, $x = 0, $y = 0) {
    bless { location => Point->new($x, $y) }, $class
  }
  use overload '""' => sub ($self, $p, $q) { "" . $self->{location} };
  sub to_string($self) { "" . $self->{location} }
  sub move($self, $x, $y) {
    $self->{location}->move($x, $y)
  }
  sub area($self) { "!!! Unimplemented !!!" }
}

package Rectangle {
  our @ISA = qw(Shape);

  sub new($class, $x = 0, $y = 0, $width = 0, $height = 0) {
    my $self = $class->SUPER::new($x, $y);
    $self->{width}  = $width;
    $self->{height} = $height;
    $self
  }
  use overload '""' => sub ($self, $p, $q) { "@{[ $self->SUPER::to_string() ]}, width:$self->{width}, height:$self->{height}" };
#  sub area($self) { $self->{width} * $self->{height} }
}

my $rct = Rectangle->new(12, 32, 100, 50);

print <<EOS;
\$rct --> $rct
\$rct->isa("Rectangle") --> @{[ $rct->isa("Rectangle") ? "true" : "false" ]}
\$rct->isa("Shape") --> @{[ $rct->isa("Shape") ? "true" : "false" ]}
\$rct->isa("Point") --> @{[ $rct->isa("Point") ? "true" : "false" ]}
EOS

$rct->move(11, 21);
say "\$rct --> $rct";
say "\$rct->area --> @{[ $rct->area ]}"
実行結果
$rct --> x:12, y:32, width:100, height:50
$rct->isa("Rectangle") --> true
$rct->isa("Shape") --> true
$rct->isa("Point") --> false
$rct --> x:23, y:53, width:100, height:50 
$rct->area --> !!! Unimplemented !!!
継承というと、メソッドをオーバーライドするのがまず頭に浮かびますが、派生クラスのメソッド中で基底クラスのメソッドを $self->SUPER::method(...) のように呼び出すことができます。
オブジェクト $rct は Rectangleクラスのインスタンスなので、$rct->isa("Rectangle") --> true
RectangleクラスはShapeクラスの派生クラスなので、$rct->isa("Shape") --> true
ShapeクラスはPointクラスを包含していますが、継承はしていないので $rct->isa("Point") --> false
$rct->area --> !!! Unimplemented !!! は、面積を返すメソッド area を Rectangle で実装していないので Shape の実装漏れチェックにランタイムで捕捉された様子。
Perlでは抽象クラスや抽象メソッドは直接はサポートされていないので、ユニットテストとクラス中のアサーションで対応することになります。
スーパークラスで実装されているオーバーロードされた演算子の呼出方法がわからなかったので to_string メソッドを定義しています。
シンボルテーブルを直接操作すればできそうですが、もう少しシンプルな方法がありそうな気がします。


例外処理

編集


この章では、Perlの例外処理について説明します。

コンピュータプログラミングにおいて、例外処理とは、プログラムの実行中に例外(特別な処理を必要とする異常な状態や例外的な状態)が発生した場合に対応する処理のことです。 例外が発生すると通常の実行フローが中断され、あらかじめ登録された例外ハンドラが実行されます。 この処理の詳細は、ハードウェア例外かソフトウェア例外か、またソフトウェア例外の実装方法によって異なります。 例外処理が提供される場合は、特殊なプログラミング言語構造、割り込みなどのハードウェア機構、またはシグナルなどのオペレーティングシステム(OS)プロセス間通信(IPC)機能によって支援されます。

Perlでは、例外処理は「特殊なプログラミング言語構造」を使用して行われます。

v5.34.0では、実験的なtry/catch 構文が追加されましたが、従来のeval {...}を使った方法を先に紹介します。


eval{} と $@ を使った例外処理

編集

Perlの組み込み関数evalは、eval STRINGの形式に加えて、eval { ... }というコードブロックを取る形式があります。 eval { ... } のコードブロック内で、ゼロ除算のような組み込み例外やdie命令によって発生するユーザー由来の例外のいずれかが発生すると、evalは処理を中止し、$@に値を設定して返します。例外が発生しなかった場合、$@はundefとなります。

eval{} と $@ を使った例外処理
use v5.30.0;
use warnings;

sub div {
    my ( $x, $y ) = @_;
    die "@{[join ':', caller]}: Domain error: div($x, $y)" if $x == 0 and $y == 0;
    $x / $y;
}

eval { div( 0, 0 ) };
warn $@ if $@;

eval { div( 1, 0 ) };
warn "@{[ join ':', (__PACKAGE__,__FILE__,__LINE__)]}: $@" if $@;
実行結果
main:Main.pl:10: Domain error: div(0, 0) at Main.pl line 6. 
main:Main.pl:14: Illegal division by zero at Main.pl line 7.
6 行目で、分子分母ともゼロの除算をユーザープログラムがドメインエラーとして例外を die 関数を使って投げ、11 行目でハンドリングしています。
7 行目で、処理系がゼロ除算を上げ、14 行目でハンドリングしています。
このように例外を発火したり捕捉したコードは、組込み関数 caller を使って呼出し元のパッケージ名:ファイル名:行番号を表示すると、例外の原因と経路を調べる役にたちます。

try{}catch{}を使った例外処理

編集

v5.34.0 で、実験的な try/catch(変数){} 構文が追加されました。 実験的なので use feature qw(try);が必要で、警告を抑止するにはno warnings "experimental::try";が必要です。

「eval{} と $@ を使った例外処理」と等価なコードを示します。

try{}catch{}を使った例外処理
use v5.34.0;
use warnings;
use feature qw(try);
no warnings "experimental::try";

sub div {
    my ( $x, $y ) = @_;
    die "@{[join ':', caller]}: Domain error: div($x, $y)" if $x == 0 and $y == 0;
    $x / $y;
}

try {
    div( 1, 0 )
}
catch ($e) {
    warn "At @{[__FILE__]} line @{[__LINE__]}: $e"
}

try {
    div( 0, 0 )
}
catch ($e) {
    warn "At @{[__FILE__]} line @{[__LINE__]}: $e"
}
実行結果
At main.plx line 16: Illegal division by zero at main.plx line 9.
At main.plx line 23: main:main.plx:20: Domain error: div(0, 0) at main.plx line 8.
8 行目で、分子分母ともゼロの除算をユーザープログラムがドメインエラーとして例外を die 関数を使って投げ、15-17 行目でハンドリングしています。
9 行目で、処理系がゼロ除算を上げ、22-24 行目でハンドリングしています。

モダンな書き方ではありますが、「実験的」という性質上、公開するモジュールや実務での使用は、実験的な性質がなくなるまで控えた方が良いでしょう。

finally{}とdefer{}

編集

v5.36.0 で、try{}catch(変数){}構文にfinally{}節が追加されましたが、やはり実験的な機能です。

[try{}catch(変数){}finallyを使った例外処理]
use v5.36.0;
use warnings;
use feature qw(try);
no warnings "experimental::try";

sub div {
    my ( $x, $y ) = @_;
    die "@{[join ':', caller]}: Domain error: div($x, $y)" if $x == 0 and $y == 0;
    $x / $y;
}

foreach my $i ( 0, 1 ) {
    foreach my $j ( 0, 1 ) {
        try {
            say "div($i, $j) --> @{[ div( $i, $j ) ]}"
        }
        catch ($e) {
            warn "At @{[__FILE__]} line @{[__LINE__]}: $e";
            next
        }
        finally {
            say "finally! \$i = $i, \$j = $j"
        }
        say "plain. \$i = $i, \$j = $j"
    }
}
実行結果
At finally.pl line 18: main:finally.pl:15: Domain error: div(0, 0) at finally.pl line 8.
finally! $i = 0, $j = 0
div(0, 1) --> 0
finally! $i = 0, $j = 1
plain. $i = 0, $j = 1
At finally.pl line 18: Illegal division by zero at finally.pl line 9.
finally! $i = 1, $j = 0
div(1, 1) --> 1
finally! $i = 1, $j = 1
plain. $i = 1, $j = 1
前節のプログラムと基本的に同じですが、分子分母をループで回しました。
例外をcatchしたときは next でループの内側の先頭に戻っています。
next の影響で say "plain. \$i = $i, \$j = $j"は例外が出ると実行されません。
しかし、finallyコードブロックのsay "finally! \$i = $i, \$j = $j"は、例外の発生有無にかかわらず実行されます。

finallyコードブロックは、例外の有無にかかわらず必ず実行する処理(例えばファイルハンドラのクローズなど)を想定していますが、同様のことはPerlのv5.36.0から導入されたdeferコードブロックでも実現できます。 deferコードブロックは、スコープが終了する時点で必ず実行され、LIFO順で実行されることが保証されています。また、deferブロック内で例外が発生した場合、それは通常の例外と同様に処理されます。

[defer{}try{}catch(変数){}を使った例外処理]
use v5.36.0;
use warnings;
use feature qw(try);
no warnings "experimental::try";

sub div {
    my ( $x, $y ) = @_;
    die "@{[join ':', caller]}: Domain error: div($x, $y)" if $x == 0 and $y == 0;
    $x / $y;
}

foreach my $i ( 0, 1 ) {
    foreach my $j ( 0, 1 ) {
        use feature 'defer';
        defer {
            say "defer! \$i = $i, \$j = $j"
        }
        try {
            say "div($i, $j) --> @{[ div( $i, $j ) ]}"
        }
        catch ($e) {
            warn "At @{[__FILE__]} line @{[__LINE__]}: $e";
            next
        }
        say "plain. \$i = $i, \$j = $j"
    }
}
実行結果
At defer.pl line 22: main:defer.pl:19: Domain error: div(0, 0) at defer.pl line 8.
defer! $i = 0, $j = 0
div(0, 1) --> 0
plain. $i = 0, $j = 1
defer! $i = 0, $j = 1
At defer.pl line 22: Illegal division by zero at defer.pl line 9.
defer! $i = 1, $j = 0
div(1, 1) --> 1
plain. $i = 1, $j = 1
defer! $i = 1, $j = 1

finally{}は、defer{}よりも例外処理の意識度が高いという意味で、コードの可読性を高めるための構文糖と言えます。 また、defer{}は、try/catchだけでなく、eval{};if($@){...}と組み合わせることもできます。

Carpモジュールのcroak関数を使ったモジュールを超えた例外

編集

Carpモジュールのcroak関数は、現在のサブルーチン(またはメソッド)を呼び出した場所を含めたスタックトレースと共に、エラーメッセージを表示してプログラムを終了します。 これにより、開発者は問題のある箇所を特定し、デバッグを行うことができます。

モジュールの場合、croak関数を使って例外をスローすることができます。 この場合、エラーメッセージにはモジュールの名前が含まれますが、例外が発生した場所はモジュール内部ではなく、モジュールを使用しているスクリプトファイルです。

以下は、例外がモジュールを超えてスローされる例です。

MyModule.pm
# MyModule.pm:

package MyModule;

use Carp qw(croak);

sub do_something {
    my ($x, $y) = @_;

    croak "Domain error" if $x == 0 and $y == 0;
    croak "Division by zero" if $y == 0;
    return $x / $y;
}

1;
main.pl
# main.pl

use MyModule;

my $result = eval { MyModule::do_something(10, 0) };
warn "An exception occurred: $@" if $@;

この例では、main.plスクリプトファイルでevalブロックを使って、MyModule::do_something関数を呼び出します。 この関数は、引数$x$yがともにゼロの場合にcroak関数を使って例外("Domain error")をスローします。 また、引数$yがゼロの場合にもcroak関数を使って例外("Division by zero")をスローします。 evalブロックは、この例外をキャッチして警告メッセージを表示します。

このように、croak関数を使うことで、モジュールを超えた例外処理が行えます。



文字コードとエンコーディング

編集

この項目では、Perlにおける文字コードとエンコーディングについて説明します。

utf8プラグマとutf8フラグ

編集

utf8プラグマを使用すると、ソースコードがUTF-8であると仮定され、文字列にutf8フラグが立つようになります。また様々な関数や演算子はバイト単位ではなく文字単位で動作するようになります。

use utf8;

例えば、lengthは文字数を返すようになります。

utf8プラグマの使用例
use v5.20.0;

use utf8;
say length "あいうえお";
no utf8;
say length "あいうえお";
実行結果

ファイルハンドルへの出力時にutf8フラグを落とすには、binmode関数で、対応するストリームのエンコーディングを指定します。

use v5.20.0;

use utf8;
binmode STDOUT, "encoding(utf-8)";
say "あいうえお";

もしくはEncodeモジュールのencode()関数を用います。

use v5.20.0;

use Encode qw(encode);

say encode('utf-8', "あいうえお");

utf8フラグが付いたまま出力すると、warningsプラグマや-wスイッチが有効な場合(use v5.12 以降は strict が[8]、 use v5.36 は、warnings がディフォルトで有効です[9])、次のような警告が発せられます。

Wide character in say at -e line 3.



ウェブアプリケーション

編集

Perlでもウェブアプリケーションを作成することは可能である。本項では、Perlでのウェブアプリケーションについて概説する。

実際のプログラミングについては別項を参照のこと。

  1. CGI

フレームワーク

編集
  • CGI::Application
  • Catalyst
  • Sledge
  • Mojolicious
  • Amon2
  • Dancer2

常駐プロセス化

編集
  • mod_perl
  • FastCGI



CGI.pm は HTML5 に対応していないなど、ここ十数年保守されていません。

PerlでCGIスクリプトを書く場合はCGIモジュールを使うことが一般的です。まずは簡単なサンプルから。

#!/usr/bin/perl
use v5.30.0;
use warnings;

use CGI;

my $title = "Simple Sample";

my $q = CGI->new;
print $q->header(-type=>'text/html', -charset=>'utf-8');
say CGI::html
  CGI::head(CGI::title $title),
  CGI::body(CGI::h1($title), CGI::p "Hello world!");
実行結果
Content-Type: text/html; charset=utf-8
 
<html><head><title>Simple Sample</title></head> <body><h1>Simple Sample</h1> <p>Hello world!</p></body></html>

CGIモジュールのheaderメソッドはHTTPレスポンスヘッダを生成するメソッドです。「-type」でContent-Typeヘッダフィールドの値を指定します。また、「-charset」に文字符号化方式を指定すれば、Content-Typeヘッダフィールドの値にcharsetを付与することができます。

クエリ文字列を解析しパラメータを取り出すには、paramメソッドを使う。

use CGI;
my $q = CGI->new;
my $title = $q->param('title'); # ?title=Perl/CGI ならば "Perl/CGI" を返す

paramメソッドにはパラメータ名を渡す。引数なしで呼び出すと、すべてのパラメータ名と値のペアをリストとして返す。

use CGI;
my $q = CGI->new;
my %param = $q->param();
print $param{title};

このように、CGIモジュールを使うことで、CGIスクリプトに必要な処理を簡略化して記述することができます。

CGI::Carp

編集

CGIスクリプトでエラーが発生した場合、サーバの設定によってはエラーログにエラーの内容が記載されるが、CGI::Carpモジュールを使うとウェブページ上にエラーメッセージを出力することができます。

use CGI::Carp qw(fatalsToBrowser);

fatalsToBrowserをインポートすると、致命的エラーが発生した場合にエラーメッセージを出力します。これにより、CGIスクリプトのデバッグが容易になります。

warningsToBrowserを呼び出すと、致命的でない警告メッセージをHTMLのコメントとして出力することができます。

use warnings;
use CGI::Carp qw(fatalsToBrowser warningsToBrowser);
warningsToBrowser(1);

CGI::Carpはdieやwarnをラップし、それらが呼び出されたときにエラーメッセージをHTMLとして出力する。モジュール内ではCarp、CGIスクリプト内ではCGI::Carpを使うことが推奨される場合があります。

-Tスイッチ

編集

CGIスクリプトでは外部からデータを渡されることが多いが、それらのデータをチェックせずに出力するなどしてクロスサイトスクリプティングなどの脆弱性を生む危険性があります。

perlに-Tスイッチを付けると、taintモードが有効となり、外部から渡された安全性が疑わしいデータを「汚染」されているものと見なす。汚染されたデータを加工せずに出力しようとすると、例外を発生させてスクリプトの動作を中断します。

#!/usr/bin/perl -T
use CGI;
my $q = CGI->new;
my $text = $q->param('text'); # $textは汚染されている
my $copy = $text; # $copyは汚染されている
$copy =~ s/&/&amp;/g; # $copyは浄化された
print $copy; # OK

-Tスイッチは脆弱性を完全に防げるものではありません。上記のコードでは、例えばMIMEタイプがtext/htmlの場合、<や>などのHTMLの構文に使われる文字をエスケープする処理を$copyに施していないため、任意の構文を埋め込むことが可能になってしまいます。

このように万全ではないものの、汚染されたデータの使用を抑制することはできるため、外部からデータを受け取るCGIスクリプトでは常に-Tスイッチを有効にすることが推奨されます。



ヘルプ・ドキュメント

編集

perldoc

編集

Perlのドキュメントを参照するには、perldocコマンドあるいはmanコマンドを実行する。perldocの日本語訳は[1]でも提供されている。



附録

編集

モジュール管理

編集

Perlでは、モジュール(ライブラリ)を活用することで、コードの再利用性を高め、作業を効率化できます。CPAN (Comprehensive Perl Archive Network) はPerlのモジュールを集めたオンラインリポジトリで、数千におよぶモジュールが公開されています。

CPANの利用方法

編集

CPANは簡単に利用可能です。次のコマンドでモジュールをインストールできます。

cpan install モジュール名

CPAN Minus (cpanm)

編集

CPANのフル機能を使う必要がない場合、軽量で素早いcpanmも便利です。以下の手順でインストールできます。

curl -L https://cpanmin.us | perl - --sudo App::cpanminus

その後、モジュールをインストールするには次のコマンドを使用します。

cpanm モジュール名

正規表現の高度な利用

編集

Perlは強力な正規表現エンジンを備えており、複雑なテキスト処理に最適です。ここでは、高度な正規表現のテクニックをいくつか紹介します。

捕捉グループと後方参照

編集

正規表現の捕捉グループ () を利用して、マッチした部分を後方参照することができます。

my $text = "abc123abc";
if ($text =~ /(abc)\d+\1/) {
    print "マッチしました!\n";
}

この例では、abc という文字列が前後に出現しているパターンをマッチさせています。

動的正規表現

編集

Perlでは変数を利用して動的に正規表現を生成できます。これは、柔軟なマッチング条件を必要とする場合に便利です。

my $pattern = 'foo';
my $text = 'foobar';
if ($text =~ /$pattern/) {
    print "マッチしました!\n";
}

Perlにおけるメモリ管理

編集

Perlはガベージコレクションを使用して自動的にメモリを管理しますが、メモリリークが発生する場合があります。以下のようなポイントに注意することで、メモリ効率を高めることができます。

弱参照

編集

循環参照がある場合、メモリリークが発生する可能性があります。Scalar::Util モジュールを使用して弱参照を作成し、循環参照を回避することができます。

use Scalar::Util 'weaken';

my $foo = {};
my $bar = { foo => $foo };
weaken($bar->{foo});  # 弱参照を作成

$foo = undef;  # メモリ解放

Perlと並列処理

編集

Perlには並列処理を行うためのモジュールがいくつかあります。例えば、forkを利用したプロセスの分岐や、スレッドを使ったマルチスレッドプログラミングが可能です。

forkによる並列処理

編集

次の例は、forkを使ってプロセスを並列で実行する方法を示しています。

my $pid = fork();
if ($pid) {
    # 親プロセス
    print "親プロセス (PID: $$)\n";
} elsif (defined $pid) {
    # 子プロセス
    print "子プロセス (PID: $$)\n";
} else {
    die "forkに失敗しました: $!";
}

スレッド

編集

threadsモジュールを使うことで、Perlスクリプトでスレッドを利用することができます。

use threads;

sub worker {
    my $id = shift;
    print "スレッド $id 実行中\n";
}

my @threads;
for my $i (1..5) {
    push @threads, threads->create(\&worker, $i);
}

$_->join() for @threads;

Perlと外部コマンドの連携

編集

Perlでは、外部コマンドを簡単に実行することができます。例えば、バッククォートを使うことで外部コマンドの結果を取得可能です。

my $output = `ls -l`;
print $output;

また、system関数を使えば、外部コマンドを実行してそのステータスを取得できます。

my $status = system("echo 'Hello, World!'");
print "終了ステータス: $status\n";

デバッグとプロファイリング

編集

Perlにはデバッグ用のツールや機能が多数用意されています。ここでは基本的なデバッグ方法を紹介します。

デバッガの利用

編集

Perlには組み込みのデバッガがあり、-d オプションでデバッガを起動できます。

perl -d スクリプト名.pl

デバッグプリント

編集

デバッグの際によく使われるのがData::Dumperモジュールです。データ構造の内容を人間が読める形で出力できます。

use Data::Dumper;
my $data = { foo => 'bar', baz => [1, 2, 3] };
print Dumper($data);

より高度なPerlの最適化

編集

Perlスクリプトのパフォーマンスを最適化するためのテクニックをいくつか紹介します。

メモリ効率の改善

編集

大規模なデータ処理では、メモリ効率が重要です。Tie::Fileモジュールを使うことで、大きなファイルを読み込む際のメモリ使用量を削減できます。

use Tie::File;
tie my @array, 'Tie::File', 'large_file.txt' or die $!;

正規表現のキャッシュ

編集

同じ正規表現を何度も実行する場合、正規表現オブジェクトをキャッシュすることでパフォーマンスを向上させることができます。

my $regex = qr/^\d+$/;
if ($input =~ $regex) {
    print "数字です\n";
}

機能の改廃

編集

Perlコアの機能改廃を中心にハイライトをまとめました。

Perl 5.40は、Perlプログラミング言語のメジャーリリースであり、いくつかの新機能、バグ修正、および改善が含まれています。主な変更点の概要を以下に示します。

新機能
__CLASS__ キーワード
このキーワードにより、メソッドまたはフィールド初期化子式内で現在のオブジェクトのクラス名にアクセスできます。
フィールド変数用の :reader 属性
この属性は、フィールド変数の値を返すメソッドを自動的に作成します。
コマンドラインオプション -M でスペースが許可されました
-M スイッチとモジュール名の間にスペースを含めることができるようになりました。
新しい論理XOR演算子 (^^)
Perl に排他的論理和比較のための新しい演算子が導入されました。
try/catch 機能は実験的ではなくなりました
この機能により、try と catch ブロックを使用してエラーを適切に処理できます。
複数の値を一度に反復する for
複数の値を同時に反復するために、for ループを使用できるようになりました。
組み込みモジュールは実験的ではなくなりました
組み込みモジュールとその関数は安定版と見なされます。
Term::Table および Test::Suite モジュールが追加されました
これらのモジュールは現在 Perl コアの一部であり、表形式化と包括的なユニット テスト機能を提供します。
セキュリティ
  • 2 つの脆弱性 (CVE-2023-47038 および CVE-2023-47039) が修正されました。
非互換性のある変更
reset EXPR がスカラに対して set-magic を呼び出すようになりました
この変更は、以前の動作に依存していたコードに影響を与える可能性があります。
未知のパッケージの import メソッドを呼び出すと警告が表示されます
これは、use ステートメントにおける潜在的な誤植の特定に役立ちます。
return は非直接オブジェクトを許可しなくなりました
return 演算子の構文は、非直接オブジェクトを拒否するようになりました。
no feature "bareword_filehandles" 下では、メソッド内のクラス裸ワードがファイルハンドルとして解決されなくなりました
この変更は、no feature "bareword_filehandles" ディレクティブ下でメソッド内でファイルハンドルとして裸ワードを使用していたコードに影響を与えます。
非推奨
  • 外側のスコープから内側のスコープにジャンプするために goto を使用する方法は非推奨となり、Perl 5.42 で削除されます。
パフォーマンスの向上
  • 否定演算子は、パフォーマンスを向上させるために最適化されました。
モジュールとプラグマ
  • Archive::TarattributesautodieBB::DeparseBenchmarkbignumbytesCompress::Raw::Bzip2Compress::Raw::ZlibCPAN::Meta::RequirementsData::Dumper、DB_FileDevel::PeekDevel::PPPortdiagnosticsDynaLoaderEncodeErrnoexperimentalExporterExtUtils::CBuilderExtUtils::ManifestExtUtils::MiniperlFcntlfeaturefieldsFile::CompareFile::FindFile::GlobFile::SpecFile::statFindBinGetopt::LongGetopt::StdHash::UtilHash::Util::FieldHashHTTP::TinyI18N::LanginfoIO、IO-CompressIO::Socket::IPIO::ZliblocaleMath::BigIntMath::BigInt::FastCalcMath::HugeIntMIME::LiteModule::BuildModule::CoreModule::Load::ConditionalModule::LoadedModule::SimpleModule::UtilNEXTNil::HandleOLE::DieOLE::StorageOpcodesParallel::ForkParse::CPAN::MetaPath::TinyperlPod::SimplePod::TextPod::WeaverProcInfoPS::SimpleXSXS::AutoLoaderx86_64strictTest::BuilderTest::CPANTest::DiagnosticsTest::HarnessTest::ManifestTest::MoreTest::PerlTest::PodTest::SimpleTime::HiResUnicode::CollateUnicode::NormalizeUnicode::XSUniversal::Autodievariablesversion など、いくつかのコアモジュールが新しいバージョンに更新されました。

これらの変更に加えて、Perl 5.40 には多くのバグ修正とマイナーな改善が含まれています。詳細については、Perl リリース ノート https://perldoc.perl.org/perldelta を参照してください。


Perl 5.38.0には多くの新機能と変更点があります。主な点は以下の通りです。

新機能
新しいクラス機能
クラスを定義するための新しい構文が実験的に導入されました。このfieldキーワードとmethodキーワードを使って、オブジェクトのデータとメソッドを定義できます。
Unicode 15.0のサポート
Unicode 15.0がサポートされました。
%{^HOOK}APIの導入
requireなどのキーワードの前後に実行されるフックを設定できる新しいAPIが追加されました。
PERL_RAND_SEED環境変数
この環境変数を設定することでrand関数の乱数の初期値をコントロールできるようになりました。
新しい正規表現変数${^LAST_SUCCESSFUL_PATTERN}
最後に一致した正規表現パターンを取得できる変数が追加されました。
変更点
非推奨警告のカテゴリ分け
非推奨の機能に関する警告がカテゴリ分けされ、個別に無効化できるようになりました。
readlineが例外フラグをリセットしなくなった
readlineが例外フラグをリセットしなくなったため、コードの挙動が変わる可能性があります。
BEGIN内でのexit()の扱い変更
BEGIN内でexit()が呼ばれた場合、それ以降のINITブロックが実行されなくなりました。
構文エラー時のふるまいの変更
構文エラーが発生した時点で解析を止め、それ以降のエラーメッセージを出力しなくなりました。
'を使ったパッケージ名の非推奨化
'をパッケージ名の区切り文字として使うことが非推奨になりました。
smartmatchの非推奨化
smartmatchオペレータ(~~)が非推奨になりました。

その他にも、多くのモジュールとドキュメントの更新、バグ修正、内部改良なども含まれています。

use v5.36 で有効になる機能
signatures
signatures が有効になりますが、依然「実験的」なままなので use feature 'signatures'; は不要になりましたが、no warnings "experimental::signatures";は必要なままです。
isa
v5.32.0 で導入されたクラスインスタンス演算子が有効になりました。
use warnings
use warnings がディフォルトで有効になりなりました。無効にするには明示的に no warnings とします。プラグマはレキシカルスコープなので、本当に警告を消したいコードブロックに限定して無効化できます。
use false true is_bool
真理値関係の組込み機能が導入されました。真理値定数と、値がブール値の性質を持つかかどうかをチェックする is_bool() が導入されました。
defer
新興言語にありがちな defer が「実験的」ですがPerlに入りました。スコープで抜けたときに実行するコードブロックをキューに登録できます。
finally block
try/catchがfinal block を取れるようになりました「実験的」(意味的には errdefer に近いです)。
use v5.36 で無効になる機能
indirect object notation
$x = new Class()記法は廃止されました。$x = Class->new()記法を使ってください。
switch
v5.10 で導入された given/when は無効化されました。
@ary = sort ()
空配列を sort 関数に渡すことは不正になりました。
use < v5.11
use v5.11より前の use v5.xx は警告を出します。v5.11.xの一部から use strict がディフォルトでになったので、use strict を強く推奨する意図があるようです。

Perl 5.34.0の新機能と変更点

コア機能強化
  • 実験的な Try/Catch 構文が追加されました。
    use feature 'try';
    
    try {
        a_function();
    }
    catch ($e) {
        warn "An error occurred: $e";
    }
  • 空の下限が正規表現の数量詞で受け入れられるようになりました。
    m/x{,3}/  # m/x{0,3}/ と等しい
  • ブレース内には空白 (タブやスペース) が自由に配置できるようになりました。(二重引用符コンテキストと正規表現パターン内で)
  • 新しいオクタルリテラル構文として 0o が導入されました。既存の 0xddddd (16進数リテラル) や 0bddddd (2進数リテラル) と同様の方法で、0o123_456 のような表記が可能です。また、組み込み関数 oct() でもこの新しい構文が使えるようになりました。
パフォーマンス向上
  • 正規表現でのメモリリークを修正しました (GH #18604)
モジュールとプラグマ
  • 新しいモジュールとプラグマが Perl コアに追加されました。
    • ExtUtils::PL2Bat 0.004
  • 既存のモジュールとプラグマが更新されました。
新機能
  • 実験的な中置演算子 'isa': この演算子は、与えられたオブジェクトが指定されたクラスまたはその派生クラスのインスタンスかどうかをテストします。
  • Unicode 13.0 サポート: https://unicode.org/versions/Unicode13.0.0/ の詳細を参照してください。
  • 連鎖比較: 比較演算子を連鎖することができます (例: if ($x < $y <= $z) {...}).
  • 新しい Unicode プロパティ 'Identifier_Status' と 'Identifier_Type': 正規表現における有効な識別子のより精密なチェックが可能になります。
  • POSIX 関数の改良: mblen(), mbtowc, wctomb がシフトステートロケールで動作し、C99 以上のコンパイラでスレッドセーフになりました。
  • アルファアサーションとスクリプトリランはもはや実験的ではない: 警告なしで使用できます。
  • 高速な機能チェック: パーサー内での機能チェックのパフォーマンスが向上しました。
  • コンパイルされたパターンのダンプ: 正規表現コンパイルの問題をデバッグするのに役立ちます。
セキュリティ修正
  • バッファオーバーフローと作成された正規表現に関連する 3 つの脆弱性が修正されました。
非互換性の変更
  • 特定の機能は、Unicode プロパティ値のワイルドカード サブパターンをコンパイルする際に許可されなくなりました。
  • 使用されていない関数 POSIX::mbstowcsPOSIX::wcstombs が削除されました。
  • (?[...]) のバグ修正により、一部のパターンがコンパイルできなくなる可能性があります。
  • \p{user-defined} プロパティは、常に公式の Unicode プロパティをオーバーライドします。
  • 定数には変更可能な変数が許可されなくなりました。
  • コードポイントが 0xFF を超える文字列に対して vec を使用することは禁止されています。
  • 文字列ビットワイズ演算子で 0xFF を超えるコードポイントを使用することは許可されなくなりました。
  • Sys::Hostname::hostname() は引数を受け付けなくなりました。
  • プレーンな "0" 文字列は、範囲演算子に対して数値として扱われます。
  • \K は、先行アサーションと後行アサーションで使用できなくなりました。
パフォーマンスの向上
  • my_strnlen, grok_bin_oct_hex, grok_number_flags、および grok_regex を含むいくつかの関数が高速化されました。

これらの変更に加えて、Perl 5.32 には多くのマイナーな修正と改善が含まれています。 詳細については、Perl 5.32 のリリースノート https://perldoc.perl.org/ を参照してください。

Perl 5.30 は、2019 年 5 月 22 日にリリースされた安定版です。Perl 5.28 からの主な変更点は以下の通りです。

機能強化
  • 正規表現における制限付き後方参照のサポート: 正規表現で、最大255文字までの制限付き後方参照アサーションが使用できるようになりました。これにより、文字列内の現在の位置より前にあるパターンに基づいてマッチングすることができます。
  • 正規表現の修飾子の上限値の引き上げ: {m,n} などの修飾子で指定できる最大値が 32767 から 65534 に引き上げられました。
  • Unicode 12.1 サポート: Perl 5.30 は、最新の Unicode 標準である Unicode 12.1 をサポートしています。
  • Unicode プロパティ値に対する部分的なワイルドカードサポート: 正規表現でワイルドカードを使用して、Unicode コードポイントをそのプロパティに基づいてマッチングできるようになりました。例えば、qr!\p{nv=/(?x) \A [0-5] \z / }! は、数値が 0 から 5 の間の Unicode 文字すべてにマッチします。
  • qr'\N{name}' のサポート: Perl は、単一引用符で囲まれた正規表現内で、名前付き文字を直接使用できるようになりました。
  • シームレスなトルコ語 UTF-8 ロケールのサポート: Perl は、トルコ語で使用される "i" や "I" などの文字の異なる大文字小文字の規則を自動的に処理します。
  • オプションの スレッドセーフなロケール操作: Perl をコンパイルして、常にスレッドセーフなロケール操作を使用するようにすることができます。
  • パフォーマンスの向上: UTF-8 処理と正規表現マッチングの高速化など、パフォーマンスを向上させるためのいくつかの最適化が行われました。
非互換性のある変更
  • $[ に非ゼロを代入することは致命的エラー: 以前は配列と文字列の開始インデックスを設定するために使用されていた $[ 変数に非ゼロ値を代入することは、現在致命的エラーとなります。
  • 正規表現における区切り文字は字形素である必要がある: 正規表現で使用される区切り文字 (例: /pattern//) は、有効な字形素 (書記体系で表現できる文字) である必要があります。
  • エスケープされていない左ブレース ({) はエラー: エスケープされていない左ブレース ({) は、文字列リテラル内でのみ許可されます。

これらの変更に加えて、Perl 5.30 には多くのバグ修正とマイナーな改善が含まれています。

use v5.28 で有効になる機能
bitwise
use feature "bitwise";することなく、 文字列固有 (&. |. ^. ~.) および数値固有 (& | ^ ~) の ビット単位演算子が使えるようになりました。

Perl 5.28.0の新機能と変更点の概要

コア機能強化
  • Unicode 10.0への対応
  • ハッシュのスライスに対するdelete命令のサポート
  • 正規表現アサーションのアルファベット別名 (実験的)
  • 混合Unicodeスクリプトの検出
  • inplace (-i) エディタの安全性向上
  • 集計状態変数の初期化
  • フルサイズのinode番号
  • sprintfの %j フォーマット修飾子 (C99以前のコンパイラで使用可)
  • close-on-exec フラグのアトミックな設定
  • 文字列/数値専用のビット演算 (実験的ではなくなった)
  • スレッドセーフなロケール (対応システム上)
  • 新しい read-only プリ定義変数 ${^SAFE_LOCALES}
セキュリティ
  • 正規表現コンパイラのヒープバッファオーバーフロー (CVE-2017-12837) の修正
  • 正規表現パーサのバッファオーバーリード (CVE-2017-12883) の修正
  • Windowsにおける $ENV{$key} のスタックバッファオーバーフロー (CVE-2017-12814) の修正
既定のハッシュ関数の変更

Perl 5.28.0では、安全性が不十分とみなされる古いハッシュ関数が廃止されました。代わりに、Siphash (2-4および1-3のバリエーション)、Zaphod32、StadtXハッシュの4つの汎用ハッシュ関数がサポートされます。さらに、短い文字列のハッシュには、提供されている他のハッシュ関数と組み合わせてSBOX32 (一種の表形式ハッシュ) がサポートされます。

互換性のない変更
  • サブルーチン属性とシグネチャの順序
  • フォーマット内でのカンマ区切りのない変数リスト
  • ロックされたおよびユニークな属性の削除
  • ブレース({})内が空の場合の \N{}
  • ファイルとディレクトリハンドルの両方としての同じシンボルのオープン
  • ベア << による <<"" の表現
  • $/ を非正の整数の参照に設定すること
  • IV_MAX を超える Unicode コードポイント
  • B::OP::terse メソッドの削除
  • 非メソッドに対する継承 AUTOLOAD の使用
  • ビット演算文字列演算子での 0xFF を超えるコードポイントを持つ文字列の使用
  • ${^ENCODING} の設定 (Perl 5.22 以降は推奨されず、Perl 5.26 以降は機能停止)
  • -S スイッチでの PATH 環境変数内でのバックスラッシュによるコロンのエスケープ
  • デバッグ H フラグ (DEBUG_H) の削除
  • ... (yada-yada) 演算子はステートメントのみ
非推奨
  • 0xFF を超えるコードポイントを持つ文字列に対する vec の使用
  • 正規表現内でのエスケープされていない "{" の一部の用法
  • "(" の直後に続くエスケープされていない "{" の使用
  • $[ への代入 (Perl 5.30 でエラー)
  • hostname() への引数の渡し (Perl 5.32 で非推奨)
モジュールの削除

いくつかのモジュールは将来のリリースで core ディストリビューションから削除される予定となっており、その時点で CPAN からのインストールが必要になります。CPAN 上でこれらのモジュールを必要とする配布パッケージは、それらを前提条件としてリストアップする必要があります。

core バージョンのこれらのモジュールは、このような事実を警告するために "deprecated" カテゴリの警告を発行するようになりました。これらの非推奨警告を消すには、問題のモジュールを CPAN からインストールしてください。

パフォーマンス向上
  • 正規表現パターン (Unicode プロパティ (\p{...})) の作成時の起動オーバーヘッドがほとんどの場合で大幅に削減されました。
  • 内部的に複数回の連結とオプションで = または .= を 1 つの操作にまとめる multiconcat オペコードが導入されたため、多くの文字列連結式が大幅に高速化されました。
  • ref()組み込み関数は、ブール値のコンテキストでは、Foo=ARRAY(0x134af48) のような一時文字列を作成しなくなったため、はるかに高速になりました。
  • その他にも多数のパフォーマンス改善が施されています。
モジュールとプラグマ

このリリースでは、いくつかのモジュールで以下のような重要なハイライトがあります。

  • use vars の削除
  • ほとんどのモジュールで XSLoader への DynaLoader の使用変更
  • モジュールの更新とプラグマ



チートシート

編集

#!/usr/bin/perl
# ↑一行目はSHEBANGでインタープリターの位置を明示。 chmod +x スクリプトしたとき、OSの image action が参照する 
# use プラグマ:
use v5.30.0;     # v5.30.0 の機能を使用する(v5.30.0より前のperlでは実行不可)。
use warnings;    # 問題となりそうな機能を警告する

# 変数宣言:
my $x;     # レキシカルスコープのスカラー変数を宣言
local $y;  # スカラーパッケージ変数 $y を退去し同じ名前の一時変数を確保 
state $z;  # レキシカルスコープの永続的な寿命を持ったスカラー変数を宣言

# データ型と接頭辞
$x;    # スカラー型
@x;    # 配列型
%x;    # ハッシュ型
&x;    # コード型

# リファレンス
# 制御構造
# 分岐
if ( 論理式 ) { ... } elsif ( 論理式 ) { ... } … else { ... }
unless ( 論理式 ) { ... } elsif ( 論理式 ) { ... } … else { ... }
# 反復
while ( 論理式 ) { ... } continue { ... }
until ( 論理式 ) { ... } continue { ... }
for ( 初期化式 ; 論理式 ; 反復式 ) { ... }
foreach my $iter ( コレクション ) { ... }  continue { ... }
# for と foreach はシノニム

#サブルーチン定義
sub subr { my ($x, $y) = @_; ... }
sub subr($$) { my ($x, $y) = @_; ... }
sub subr :pritotype($$) { my ($x, $y) = @_; ... }
sub subr($x,$y) { ... }

# 正規表現
/./              # 任意の一文字
/[A-Z]/          # 英字大文字
/[a-z]/          # 英字小文字
/[0-9]/ == /\d/  # 数字
/[^0-9]/ == /\D/ #

# よく使う関数
print ();
say ();

# 演算子
$x + $y;          # 和 
$x - $y;          # 差
$x * $y;          # 積
$x / $y;          # 商
$x % $y;          # 剰余
$x ** $y;         # 累乗
$s1 . $s2;        # 連結
$s x $n;          # 繰返し


脚註

編集
  1. ^ PHPやRubyではハッシュも順序は保証されます。Perlでは、このような用途に Tie::Hash モジュールを使います
  2. ^ Algorithmic Complexity Attacks
  3. ^ Operator Precedence and Associativity
  4. ^ 4.0 4.1 Perl5.32から0<= $x && $x < 120<= $x < 12 書けるようになりました。
  5. ^ perlsecret - Perl secret operators and constants
  6. ^ 暗黙のstrict
  7. ^ use v5.36
  8. ^ 暗黙のstrict
  9. ^ use v5.36

外部リンク

編集

Perlのサブページ一覧

編集

特別:Prefixindex/Perl/