ぱいぱいにっき

Pythonが好きすぎるけれど、今からPerlを好きになりますにっき

30年分の後方互換性を保ちながら進化し続けるための言語機能

この記事はPerl Advent Calendar 2024 17日目の記事です。昨日はkarupaneruraさんのString::Secretのご紹介でした。演算子オーバーロードで文字列でないものを文字列に見せかけるのは僕もやったことがありました。

さて、argathさんがAdvent Calendarの13日目に次のPerlはPerl 42(かも)という記事を書いていました。この記事からリンクされているPerl 5 is Perlという記事を読んでみると、

30年前の1994年10月17日に5.0になりました。

とあります。つまり、現在広く使われているPerl 5はリリースから30年を過ぎたということになります。これはとんでもないことですし、この記事にはこれからも進化を続けていく旨が書かれています。あとよくPerlを使わない方から聞かれる点として、Perl 6はいつリリース/移行されるのかという話ですが、Perl 6は既にリリースされた上で、Rakuという名前に変わっています。リリースされた時点でそうですが、Perl 5の後継はPerl 6ではなく、姉妹的な言語であるとされています。

ところで、最新のバージョンである5.40やそれより前の5.38,5.36,5.34には多くの機能が追加されています。目立ったもので言うと以下のような感じです。

  • try/catch/finally
  • class
  • true/false
  • for my ($first, $second) (@list)

使えなくなったものの例: 間接オブジェクト記法

一方で、使えなくなったものもあります。代表例としては、間接オブジェクト記法があります。厳密には使えないわけではなく、最新のPerlの機能を有効にする use v5.40; をスクリプト内に記述すると使えなくなります。どういうことか。

まず間接オブジェクト記法をおさらいします。以下のコードでは、Fooクラスを定義し、インスタンスを作成するnewメソッドと、helloメソッドを定義しています。そしてそれらを呼び出しています。 間接オブジェクト記法とは以下のコード中では new Foo と hello Foo にあたります。

use strict;
use warnings;
use utf8;

package Foo {
  sub new {
    my $class = shift;
    my %obj = (name => "Fooooo");
    bless \%obj, $class;
  }
  sub hello {
    my $self = shift;
    print $self->{name} . ": hello\n";
  }
}

my $foo = new Foo;
hello $foo;

ところで、広く使われるPerlはこのようにクラスのメソッド呼び出しを扱いません。一般的にはこうです。

my $foo = Foo->new;
$foo->hello;

つまり間接オブジェクト記法はアロー演算子->ではなく、Javaなどに近い記法でメソッド呼び出しを行う糖衣構文であると言えます。

一方で、use v5.40;を行うと上記のコードはどうなるでしょうか。strictとwarningsプラグマの代わりにv5.40プラグマを入れます。(なぜならv5.40にこれら2つのプラグマは含まれるからです)

use v5.40;
use utf8;

package Foo {
  sub new {
    my $class = shift;
    my %obj = (name => "Fooooo");
    bless \%obj, $class;
  }
  sub hello {
    my $self = shift;
    print $self->{name} . ": hello\n";
  }
}

my $foo = new Foo;
hello $foo;

このコードは動きません。以下にエラーを示します。

Bareword found where operator expected (Do you need to predeclare "new"?) at prog.pl line 16, near "new Foo"
syntax error at prog.pl line 16, near "new Foo"
Execution of prog.pl aborted due to compilation errors.

newという関数がないと起こられています。この書き方でnewをFooのメソッドであると認識することはなくなり、単なる関数呼び出しとして解釈しようとしていることがわかります。

ではなぜ、間接オブジェクト記法はなくなってしまったのでしょうか? Perldocの間接オブジェクト記法の説明にしっかりと書かれています。

つまり、上記のコードだと、newやhelloが関数なのか、それともメソッドなのかを区別するのが読む側にも難しく、またインタプリタに対しても間違った指示を与えてしまうことがあります。

Try::Tinyを使おうとしたら動くのに、挙動が意図通りにならないという例もあります。

techblog.karupas.org

そんなこんなで、間接オブジェクト記法は当時は書き方がかっこよかったものの(特にnewメソッド)、今となっては非推奨になり、さらにuse v5.36;をすると無効化されるということになりました。

Perlは実は過去に実装されて広く使われているものの、間違いが起こりやすいなどのよくない文法も無効化されるようになってきているんですね。めでたしめでたし。

後方互換性を守る仕組み

これだと他の言語でもよくあることですよね? しかし後方互換性を鬼守るPerlでは少し違います。

さっき言ったように最新のインタプリタであるperl 5.40であってもuse v5.36;以上をしなければ間接オブジェクト記法は使用可能です。厳密には、use feature 'indirect';とすると使用可能です。

そして、間接オブジェクト記法を使用しているコードは無視できない程度には存在すると思われます。それを新しい記法を使いたいから、もしくは自分が間違えてしまうので間接オブジェクト記法を無効化すると既存コードも壊れてしまうということになってしまうと思いませんか?

しかし、Perlの機能スイッチを行うuse VERSION;はスコープに効くという特徴があります。ファイルではありません、スコープに効きます。以下のコードを見てみましょう。

use strict;
use warnings;
use utf8;

package Foo {
  sub new {
    my $class = shift;
    my %obj = (name => "Fooooo");
    bless \%obj, $class;
  }
  sub hello {
    my $self = shift;
    print $self->{name} . ": hello\n";
  }
}

my $foo = new Foo;
hello $foo;

{
  use v5.40;
  my $foo2 = new Foo;
  hello $foo2;
}

my $foo3 = new Foo;
hello $foo3;

Perlは{ ... }でブロックスコープを作れます。以上のコードではブロックスコープ内でuse v5.40;を宣言しています。このコードは以下のエラーが出ます。

Bareword found where operator expected (Do you need to predeclare "new"?) at prog.pl line 22, near "new Foo"
syntax error at prog.pl line 22, near "new Foo"
Execution of prog.pl aborted due to compilation errors.

エラーで指摘されている22行目はmy $foo2 = new Foo;にあたります。もっと手前のmy $foo = new Foo;ではエラーが出ていません。そして、22行目をmy $foo2 = Foo->new; 23行目を $foo2->hello;に変えると正常に動作します。use v5.40;の及ぶ範囲がブロックスコープに限定されていることがわかります。

また、このスコープとはレキシカルスコープです。どういうことかというと、use v5.40;と宣言したスコープから宣言していないスコープの関数を呼び出したとしても、呼び出し元のスコープのuse v5.40;は呼び出し先のスコープには適用されません。

つまり以下のコードは動きます。

sub hello_foo {
  my $foo = new Foo;
  hello $foo;
}

{
  use v5.40;
  hello_foo;
}

このことから、自分が新たにいじる範囲のみをuse v5.40;とすることで、間接オブジェクト記法のめんどくささから解放され、また既存のコードには手を入れずに修正することが可能になります。

一方で、ダウングレードには現在一部のバージョンの組み合わせではエラーになり、その他の組み合わせでは警告が出るようになっています。ダウングレードは後方互換性の観点ではまず使わないとは思いますが、ご注意ください。

いかがでしたでしょうか。このほかにもuse VERSION;は言語機能のスイッチをバージョンで動的に管理するなど、部分部分で新しい機能を使えるようにしつつ、既存コードはそのままにできる設計になっています。もし10年以上動かさないといけないが、ちょっとしたスクリプト言語で書きたい場合にPerlを選択するのは、意外と理にかなった選択かもしれません。

明日18日目はxtetsujiさんで「そろそろソースフィルタに挑戦してみたいなぁと」です。気になりますね。