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

去る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) にて扱われる。

コメント

このブログの人気の投稿

js_of_ocaml の使い方

js_of_ocaml (jsoo) は Ocsigen が提供しているコンパイラである。その名の通り OCaml バイトコードから JavaScript コードを生成する。 これを使うことで OCaml で書いたプログラムを Web ブラウザや node.js で実行することができる。 インストール 単に OPAM を使えば良い: $ opam install js_of_ocaml js_of_ocaml-ocamlbuild js_of_ocaml-ppx バージョン 3.0 から OPAM パッケージが分割されたので、必要なライブラリやプリプロセッサは個別にインストールする必要がある。 とりあえず使うだけなら js_of_ocaml と js_of_ocaml-ppx の二つで十分。後述するように OCamlBuild でアプリケーションをビルドするなら js_of_ocaml-ocamlbuild も入れると良い。 これで js_of_ocaml コマンドがインストールされ、OCamlFind に js_of_ocaml 及びサブパッケージが登録される。 コンパイルの仕方 以下ソースファイル名は app.ml とし、ワーキングディレクトリにあるものとする。 手動でやる場合 一番安直な方法は、直接 js_of_ocaml コマンドを実行することである: $ # バイトコードにコンパイルする。js_of_ocaml.ppx は JavaScript オブジェクトの作成や操作の構文糖衣を使う場合に必要 $ ocamlfind ocamlc -package js_of_ocaml,js_of_ocaml.ppx -linkpkg -o app.byte app.ml $ # 得られたバイトコードを JavaScript にコンパイルする $ js_of_ocaml -o app.js app.byte OCamlBuild を使う場合 OCamlBuild を使う場合、.js 用のビルドルールを定義したディスパッチャが付属しているので myocamlbuild.ml でこれを使う: let () = Ocamlbuild_plugin . dispatch Ocamlbuild_js_of_ocaml . dispatcher $ # app.ml -...

Project Euler - Problem 3

問題 原文 What is the largest prime factor of the number 600851475143 ? 日本語訳 600851475143 の素因数のうち最大のものを求めよ。 解答 いきなり難易度が上がりました。素因数分解の問題です。 結局のところ片っ端から割ってみるしかないのですが、探索範囲を狭めることはできます。 正の整数の範囲で考えると、l = m * nかつm >= nであれば、floor(sqrt(l)) >= nであることが直感的に分かります。ここでfloorは実数を0の方向に丸めて整数にする関数、sqrtは平方根を返す実数関数です。 nが分かれば、mは除算で求められます。つまりlが与えられた時、それを2つの因数に分解するのに探索する範囲は高々2 .. floor(sqrt(l))で十分ということです。 2つに分けられたならこっちのもの、得られたmとnを再帰的に分解して、その結果を合わせれば素因数分解の結果が得られます。 アルゴリズムを書き下すと次のようになります: n <- floor(sqrt(l))とする。 n = 1なら、これ以上は分解できない(lは素数である)のでlをそのまま返す。 nがlの因数なら、m <- l / nとし、mとnに対してこのアルゴリズムを再帰的に適用し、その結果を連結して返す。 因数でないなら、n <- n - 1とし、2.から繰り返す。 これで計算は可能です。しかしもう少し最適化を検討してみましょう。 偶数は一般に2mの形で表せます。同様に奇数は2m + 1と書けます。 偶数同士の積は2m * 2n = 4mnとなり、l = 2mnとおけば2lなのでこれも偶数です。 奇数同士の積は(2m + 1)(2n + 1) = 4mn + 2(m + n) + 1であり、l = 2mn + m + nとすると2l + 1なのでこれも奇数です。 偶数と奇数の積は2m(2n + 1) = 4mn + 2mなので、l = 2mn + mとおいて2lなので偶数です。 つまり、奇数の因数は必ず奇数のみから成るということが分かります。先ほどのアルゴリズムでは、4.のステップでn <- n - 1としていました。...

(multi-)term-mode に dirtrack させる zsh の設定

TL;DR .zshrc に以下を書けば良い: # Enable dirtrack on (multi-)term-mode. if [[ " $TERM " = eterm * ]]; then chpwd() { printf '\032/%s\n' " $PWD " } fi 追記 (May 14, 2025): oh-my-zsh を使っていれば emacs プラグインが勝手にやってくれる: plugins = ( emacs ) 仔細 term-mode は Emacs 本体に付属する端末エミュレータである。基本的には Emacs 内でシェルを起動するために使うもので、古い shell-mode よりも端末に近い動きをするので便利なのだが、一つ問題がある。シェル内でディレクトリを移動しても Emacs バッファの PWD がそのままでは追従しない点だ。 こういう追従を Emacs では Directory Tracking (dirtrack) と呼んだりするが、 shell-mode や eshell ではデフォルトで提供しているのに term-mode だけそうではない。 要するにシェル内で cd してもバッファの PWD は開いた時点のもの (基本的には直前にアクティヴだったバッファの PWD を継承する) のままなので、移動したつもりで C-x C-f などをするとパスが違ってアレっとなることになる。 実は term-mode にも dirtrack 機能自体は存在しているのだが、これは シェルがディレクトリ移動を伴うコマンドを実行したときに特定のエスケープシーケンスを含んだ行を印字することで Emacs 側に通知するという仕組み になっている。 Emacs と同じく GNU プロジェクトの成果物である bash は Emacs 内での動作を検出すると自動的にこのような挙動を取るが、zsh は Emacs の事情なんか知ったことではないので手動で設定する必要がある。 まずもって「ディレクトリ移動のコマンドをフックする」必要がある訳だが、zsh の場合これは簡単で cd / pushd / popd のようなディレクトリ...

Project Euler - Problem 27

問題 しばらく止まってましたが今日から再開。 原文 Considering quadratics of the form: n 2 + an + b, where |a| < 1000 and |b| < 1000 Find the product of the coefficients, a and b, for the quadratic expression that produces the maximum number of primes for consecutive values of n, starting with n = 0. 日本語訳 |a| < 1000, |b| < 1000 として以下の二次式を考える (ここで|a|は絶対値): n 2 + an + b n=0から始めて連続する整数で素数を生成したときに最長の長さとなる上の二次式の, 係数a, bの積を答えよ. 解答 最大探索範囲は-999 <= a <= 999、-999 <= b <= 999なので、およそ4,000,000通りの係数の組合せを試すことになります。組合せ毎に数列を生成して、それが素数か判定するわけですからたまりません。簡単な検討を加えて範囲を絞りましょう。 与えられた二次式をf(n)とおくと、f(0) = b、f(1) = a + b + 1です。 f(n)が長さ2以上の素数列を生成するならこれらは素数ですから、次のことがいえます: bは素数である a + b + 1は素数である b = 2のとき、aは偶数である それ以外のとき、aは奇数である 素数判定関数 is_prime には同じ引数が与えられることがよくあるのでメモ化しています。 #!/usr/bin/perl use strict; use warnings; use feature qw/say/; sub prime_seq_len($$) { my ($coeff_a, $coeff_b) = @_; my $len = 0; my $n = 0; $len++, $n++ while is_prime($n * ($n + $coeff_a) ...

救急外来にかかったときの記録

子どもの頃にかかった記憶はあるが自分で行ったことはなかったのでメモしておく。 先日怪我をした。より具体的に云うとランニング中に転倒し顎を地面に叩きつけた。深夜の12時ごろの話である。 その時点ては両手の擦傷が痛いとか下顎の間接が痛いとか奥歯のセラミックが割れなくて幸いだったといった程度だが、マスクを外して見るとなにやら下部に血がついている。 顎にも擦傷があるのかとうんざりしながら歩いて帰り、血の滲んだマスクを捨てて傷口を洗おうとしたところで皮膚が割けて肉が見えているのに気付いた。 一瞬顔が青くなったが単身なので倒れるわけにはいかない。幸い血は固まっていてそれほど出血していないし、先程まで運動していたからかあまり痛みもない。 この時点で明白な選択肢は3つあった。即ち: 救急車を呼ぶ 自力で病院へ行き救急外来を受診する 応急処置して朝になったら近場の医院を受診する である。まず 3 は精神的に無理だと悟った。血も完全には止まっていないし、痛みだしたら冷静に行動できなくなるだろう。 1 はいつでも可能だったが、意識明瞭で移動にも支障がない状態では憚られた。救急車が受け入れ先病院を探すのにも時間がかかると聞く。 結局とりあえず 1 をバックアップ案とし、2 の自分で連絡して病院へ向かうことにした。まずは病院探しである。このときだいたい 00:30 AM。 最初に連絡したのは最寄りの都立病院の ER だった。ここならタクシーで10分もかからない、のだが、なんと ER が現在休止しているとの回答だった。そんなことがあるのかと驚愕したがどうしようもない。 近場に形成外科の救急外来の開いている病院はないか尋ねたところ 消防庁の相談センター の電話番号を案内された。 ここで4つの病院を紹介された。余談だが相談の対応は人間だが番号の案内は自動音声に切り替わるので録音の用意をした方が良い (一応2回くり返してくれる。) いずれも若干遠くタクシーで2、30分かかるが仕方がない。最初に連絡した最寄りの病院はその日形成外科の当直医師がいなかった。二件目でトリアージの質問をされ、受け入れ可能とのことだったので受診先が決定。このとき 00:45 AM。 診察時に脱ぎ易い服に着替え (このときまでランニングウェアだった)、健康保険証を持って病院へ向かう。ガーゼがないのでマス...