スキップしてメイン コンテンツに移動

去る6月に Perl 5.32.0 がリリースされたので差分を把握するために perldelta を読んだ件

要旨

Perl 5 メジャーバージョンアップの季節がやって来たのでまともな Perl プログラマの嗜みとして perldelta を読んだ。

今回は有り体に言えばルーティン的なリリースで、言語コアの拡張は他言語にも見られる構文が実験的に入ったくらいで大きな変化はない。新機能は RegExp の拡充が主である。

比較的重要と思われる変更点を抜粋する。

新機能

isa 演算子

実験的機能。Python とか Java における isinstance とか instanceof

これまでも UNIVERSAL::isa があったが、これはメソッドなのでレシーバにオブジェクトでもクラスでもない値 (i.e., 未定義値 / bless されていないリファレンス) を置くと実行時エラーが起きるのが問題だった:

package Foo {
  use Moo;
}

package Bar {
  use Moo;
  extends 'Foo';
}

package Baz {
  use Moo;
}

use feature qw/say/;

sub do_something_with_foo_or_return_undef {
  my ($foo) = @_;

  # Returns safely if the argument isn't an expected instance, in mind.
  return unless $foo->isa('Foo');
  ...;
}

# OK.
do_something_with_foo(Bar->new);

# |undef| is expected in mind, but actually error will be thrown.
do_something_with_foo(undef);

これを避けるために今までは Scalar::Util::blessed を併用したりしていたわけだが、isa 演算子は左辺が何であっても意味のある値を返すのでよりシンプルになる:

# True
+(bless +{} => 'Foo') isa Foo;

# False
undef isa Foo;

# False
+{} isa Foo;

# False
+(bless +{} => 'Baz') isa Foo;

比較演算子の連結

Python のアレ。このために比較演算子の結合性が「連結 (chained)」に変更された。

my $x = rand(10);
say 0 <= $x < 5 ? '< 5' : '>= 5';
use Math::Round qw/nearest/;

sub PI() { 4 * atan2(1, 1) }

# Rounds to the second decimal place.
sub round($) { nearest(0.01, $_[0]) }

say round sin(0) == round cos(PI / 2) == round sin(PI) != round cos(PI)
  ? 'sin(0) = cos(π / 2) = sin(π) ≠ cos(π)'
  : 'You are in wrong universe.';

連言で繋いだ場合と比べて連結された中間の式の評価回数が一回減ることに注意が必要である; A <= B < C の式 B は一回しか評価されないのに対して、A <= B and B < C の場合は高々二回評価される。

my $x = 40;
say $x if 0 <= ++$x < 42;  # 41

my $y = 40;
say $y if 0 <= ++$y and ++$y < 42;  # Doesn't print.

副作用のある式を混ぜる方がどうかしているといえばそれまでだが。

Unicode Name プロパティ参照

\p{Name=...} で Unicode 文字を名前 (e.g., “LATIN CAPITAL LETTER A”)で参照できるようになった。これまでも \N があったが、主な違いは文字列補間が効くことと、名前に対して副パターンでマッチングできる (e.g., (qr!\p{Name=/LATIN CAPITAL LETTER [A-F]/}!)) ことである。

実験的機能の正式化

Perl 5.28 で実験的機能として導入された正規表現パターンがいくつか標準で警告なしに使えるようになった。

Script Run

ドメイン名スプーフィング (ラテン文字とキリル文字など見た目に区別しづらい字形の文字を混ぜて権威あるサイトに見せかける手法) を検出するのに有用な機能。 (*script_run:...) ないし (*sr:...) で囲んだパターンが同一の Unicode Script にある文字で成り立っていない場合バックトラックする。ただし日常的に複数の Script を使う東アジアのいくつかの言語の文字は少し特別扱いされる。

ラテン文字による別名

既存の拡張正規表現に説明的な別名を付ける試み。コア言語の特殊グローバル変数に対する English.pm のような関係だがこれは新しい構文と一緒に導入されたので特に宣言なく利用可能である。

Symbolic Alias(es)
(?=...) (*pla:...) / (*positive_lookahead:...)
(?!...) (*nla:...) / (*negative_lookahead:...)
(?<=...) (*plb:...) / (*positive_lookbehind:...)
(?<!...) (*nlb:...) / (*negative_lookbehind:...)
(?>...) (*atomic:...)

非互換な変更

字句的定数関数内の変更され得る字句的スコープ変数参照の違法化

Perl は 0 引数で暗黙に値を返す関数を定数としてインライン化するが、実はこの最適化は戻り値として字句的スコープ変数を参照するクロージャにも適用される。 一度インライン化された値は実行時に変更しても反映されないので、定数と認識されたクロージャは一般的なクロージャとは異なる挙動をすることになる:

my $x = 42;
# Constant.
my $K = sub () { $x };
# Closure; Avoiding optiomization by explicit |return|.
my $L = sub () { return $x };

say $K->() + 1;  # 43
say $L->() + 1;  # 43

$x = 0;
say $K->() + 1;  # 43 (!)
say $L->() + 1;  # 1

端的に言って最適化器のバグなのだが、Perl は意図した機能かそうでないかに関らず現実に用例がある挙動は変えないのが伝統であった。公式にこの方針が転換されたのは Perl 5.14 からで、実際に廃止予定 (deprecated) 機能の廃止ロードマップ (perldeprecation) が示されたのは Perl 5.26 からである。

この機能に関しては経過措置として Perl 5.22 から警告が出ていたが予定通り廃止された。今後はこのようなサブルーチンを定義することは単に違法であり、コンパイル時に致命的エラーとなる

なお変更され得ない字句的スコープ変数についてはこれまでどおり参照して良い。ライフサイクルを通して以下のような「変更され得る」操作を受けないのが条件である:

# Error.
$x = 0;

# Error; No matter even if the branch is never reached.
$x = 0 if 0;

# Error; Subroutines can take aliases of arguments so it can be altered.
proc($x);

また字句的スコープでない変数を参照している場合や、クロージャでないサブルーチン定義の場合はそもそもインライン化されないので関係がない:

my $x = 42;
our $y = 42;

# OK; |$y| is not a lexical variable.
my $K = sub () { $y };

# OK; Global subroutines referencing variables are not inlined.
sub L() { $x }

# OK; Ditto, even if lexical subroutines.
use feature qw/lexical_subs/;
my sub M() { $x }

その他

GitHub への移行

https://github.com/Perl/perl5 が Perl 5 のプライマリなリポジトリになった。開発も GitHub の Issues / PRs を使って行われるようになった。 ただし脆弱性の報告は相変わらず非公開のバグトラッカーとメーリングリスト (cf. perldoc perlsec) にて扱われる。

コメント

このブログの人気の投稿

京大テキストコーパスのパーサを書いた

要旨 CaboCha やなんかの出力形式であるところの京大テキストコーパス形式のパーサモジュールを Perl で書いたので紹介します。 Github Tarball on Github Ppages これを使うと例えば CaboCha の出力した係り受け関係を Perl のオブジェクトグラフとして取得できます。 使用例 単なる文節区切りの例。 #!/usr/bin/env perl use v5.18; use utf8; use IPC::Open3; use Parse::KyotoUniversityTextCorpus; use Parse::KyotoUniversityTextCorpus::MorphemeParser::MeCab; use Symbol qw//; my ($in, $out, $err); my $pid; BEGIN { ($in, $out, $err) = (Symbol::gensym, Symbol::gensym, Symbol::gensym); $pid = open3($in, $out, $err, cabocha => '-f1'); } END { close $out; close $err; waitpid $pid => 0 if defined $pid; } binmode STDOUT, ':encoding(utf8)'; binmode $in, ':encoding(utf8)'; binmode $out, ':encoding(utf8)'; my $parser = Parse::KyotoUniversityTextCorpus->new( morpheme_parser => Parse::KyotoUniversityTextCorpus::MorphemeParser::MeCab->new, ); say $in '星から出るのに、その子は渡り鳥を使ったんだと思う。'; say $in '出る日の朝、自分の星の片付けをした。'; close $in; my $sentence...

Perl 7 より先に Perl 5.34 が出るぞという話

Perl 5 の次期バージョンとして一部後方互換でない変更 (主に間接オブジェクト記法の削除とベストプラクティスのデフォルトでの有効化) を含んだメジャーバージョンアップである Perl 7 がアナウンスされたのは昨年の 6 月 のことだったが、その前に Perl 5 の次期周期リリースである Perl 5.34 が 5 月にリリース予定 である。 現在開発版は Perl 5.33.8 がリリースされておりユーザから見える変更は凍結、4 月下旬の 5.33.9 で全コードが凍結され 5 月下旬に 5.34.0 としてリリース予定とのこと。 そういうわけで事前に新機能の予習をしておく。 8進数数値リテラルの新構文 見た瞬間「マジかよ」と口に出た。これまで Perl はプレフィクス 0 がついた数値リテラルを8進数と見做してきたが、プレフィクスに 0o (zero, small o) も使えるようになる。 もちろんこれは2進数リテラルの 0b や 16進数リテラルの 0x との一貫性のためである。リテラルと同じ解釈で文字列を数値に変換する組み込み関数 oct も` 新構文を解するようになる。 昨今無数の言語に取り入れられているリテラル記法ではあるが、この記法の問題は o (small o) と 0 (zero) の区別が難しいことで、より悪いことに大文字も合法である: 0O755 Try / Catch 構文 Perl 5 のリリース以来 30 年ほど待たれた実験的「新機能」である。 Perl 5 における例外処理が特別な構文でなかったのは予約語を増やさない配慮だったはずだが、TryCatch とか Try::Tiny のようなモジュールが氾濫して当初の意図が無意味になったというのもあるかも知れない。 use feature qw/ try / ; no warnings qw/ experimental::try / ; try { failable_operation(); } catch ( $e ) { recover_from_error( $e ); } Raku (former Perl 6) だと CATCH (大文字なことに注意) ブロックが自分の宣言されたスコープ内で投げられた例外を捕らえる...

C の時間操作関数は tm 構造体の BSD 拡張を無視するという話

久しぶりに C++ (as better C) で真面目なプログラムを書いていて引っかかったので備忘録。 「拡張なんだから標準関数の挙動に影響するわけねえだろ」という常識人は読む必要はない。 要旨 time_t の表現は環境依存 サポートしている時刻は UTC とプロセスグローバルなシステム時刻 (local time) のみで、任意のタイムゾーン間の時刻変換を行う標準的な方法はない BSD / GNU libc は tm 構造体にタイムゾーン情報を含むが、tm -> time_t の変換 ( timegm / mktime ) においてその情報は無視される 事前知識 C 標準ライブラリにおいて時刻の操作に関係するものは time.h (C++ では ctime) ヘッダに定義されている。ここで時刻を表現するデータ型は2つある: time_t と tm である。time_t が第一義的な型であり、それを人間が扱い易いように分解した副次的な構造体が tm という関係になっている。なので標準ライブラリには現在時刻を time_t として取得する関数 ( time_t time(time_t *) ) が先ずあり、そこから time_t と tm を相互に変換する関数が定義されている。 ここで time_t の定義は処理系依存である。C / C++ 標準はそれが算術型であることを求めているのみで (C11 からは実数型に厳格化された)、その実体は任意である。POSIX においては UNIX epoch (1970-01-01T00:00:00Z) からのうるう秒を除いた経過秒数であることが保証されており Linux や BSD の子孫も同様だが、この事実に依存するのは移植性のある方法ではない。 一方で tm は構造体であり、最低限必要なデータメンバが規定されている: int tm_year : 1900 年からの年数 int tm_mon : 月 (0-based; 即ち [0, 11]) int tm_mday : 月初からの日数 (1-based) int tm_hour : 時 (Military clock; 即ち [0, 23]) int tm_min : 分 int tm_sec : 秒 (うるう秒を含み得るので [0...

Perl のサブルーチンシグネチャ早見表

Perl のサブルーチン引数といえば実引数への参照を保持する特殊配列 @_ を手続き的に分解するのが長らくの伝統だった。これはシェルの特殊変数 $@ に由来する意味論で、おそらく JavaScript の arguments 変数にも影響を与えている。 すべての Perl サブルーチンはプロトタイプ宣言がない限りリスト演算子なので、この流儀は一種合理的でもあるのだが、実用的にそれで良いかというとまったくそうではないという問題があった; 結局大多数のサブルーチンは定数個の引数を取るので、それを参照する形式的パラメータが宣言できる方が都合が良いのである。 そういうわけで実験的に導入されたサブルーチンシグネチャ機能により形式的パラメータが宣言できるようになったのは Perl 5.20 からである。その後 Perl 5.28 において出現位置がサブルーチン属性の後に移動したことを除けば Perl 5.34 リリース前夜の今まで基本的に変わっておらず、未だに実験的機能のままである。 おまじない シグネチャは前方互換性を持たない (構文的にプロトタイプと衝突している) 実験的機能なのでデフォルトでは無効になっている。 そのため明示的にプラグマで利用を宣言しなければならない: use feature qw/signatures/; no warnings qw/experimental::signatures/; どの途みんな say 関数のために使うので feature プラグマは問題ないだろう。実験的機能を断りなしに使うと怒られるので、 no warnings で確信犯であることをアピールする必要がある。 これでプラグマのスコープにおいてサブルーチンシグネチャ (と :prototype 属性; 後述) が利用可能になり、 従来のプロトタイプ構文が無効になる。 使い方 対訳を載せておく。シグネチャの方は実行時に引数チェックを行うので厳密には等価でないことに注意: # Old School use feature qw/signatures/ 1 sub f { my ($x) = @_; ... } sub f($x) { ... } 2 sub f { my ($x, undef, $y) = @_...