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

Perl 5 to 6 - カスタム演算子

これはMoritz Lenz氏のWebサイトPerlgeek.deで公開されているブログ記事"Perl 5 to 6" Lesson 13 - Custom Operatorsの日本語訳です。

原文はCreative Commons Attribution 3.0 Germanyに基づいて公開されています。

本エントリにはCreative Commons Attribution 3.0 Unportedを適用します。

Original text: Copyright© 2008-2010 Moritz Lenz

Japanese translation: Copyright© 2011 SATOH Koichi

NAME

"Perl 5 to 6" Lesson 13 - カスタム演算子

SYNOPSIS

multi sub postfix:<!>(Int $x) {
    my $factorial = 1;
    $factorial *= $_ for 2..$x;
    return $factorial;
}

say 5!;                     # 120

DESCRIPTION

演算子は変わった名前を持ち、優先度とか結合性のような付加的な属性が少しだけ付いた関数です。 Perl6は通常term infix termというパターンに従います。termは前置演算子が前に付いていたり、後置演算子や後置接周(postcircumfix)演算子が後に付いたりしていても構いません。

1 + 1               中置
+1                  前置
$x++                後置
<a b c>             接周
@a[1]               後置接周

演算子の名前は「特別な」文字に限らず、空白以外なら何でも使えます。

演算子の長い名前はそのタイプの後にコロンとリテラルあるいはシンボルのリストが付きます。 例えばinfix:<+>1+2で使われている演算子です。 もう一つの例はpostcircumfix:<[ ]>で、これは@a[0]の演算子です。

これまで得た知識を使えば、もう新しい演算子を定義できます:

multi sub prefix:<€> (Str $x) {
    2 *  $x;
}
say €4;                         # 8

優先度

$a + $b * $cのような式において、infix:<*>演算子はinfix:<+>演算子よりきつい優先度を持ちます。 これが式が$a + ($b * $c)と解釈される理由です。

新しい演算子の優先度は既存の演算子との比較で与えることができます:

multi sub infix:<foo> is equiv(&infix:<+>) { ...  }
mutli sub infix:<bar> is tighter(&infix:<+>) { ... }
mutli sub infix:<baz> is looser(&infix:<+>) { ... }

結合性

ほとんどの中置演算子は引数を2つだけ取ります。1 / 2 / 4のような式では、評価の順序は結合性に拠って決められます。 infix:</>演算子は左結合なので、この式は(1 / 2) / 4と解析されます。 infix:<**>(べき乗)のように右結合の演算子の場合、2 ** 2 ** 42 ** (2 ** 4)と解析されます。

Perl6には更に多くの結合性があります: noneは同じ優先度の演算子の結合を禁止します(例えば2 <=> 3 <=> 4は禁止されています)。 またinfix:<,>はリスト結合性を持ちます。1, 2, 3infix:<,>(1; 2; 3)と解釈されます。 最後に連結結合性があります: $a < $b < $c($a < $b) && ($b < $c)と解釈されます。

multi sub infix:<foo> is tighter(&infix:<+>)
                      is assoc('left')
                      ($a, $b) {
    ...
}

後置接周と接周

後置接周演算子はメソッド呼び出しです:

class OrderedHash is Hash {
    method postcircumfix:<{ }>(Str $key) {
        ...
    }
}

$object[$stuff]のようにして呼び出すと$stuffがメソッドの引数として渡され、$objectselfとして参照可能になります。

接周演算子は通常と違った構文(my @list = <a b c>;のような)に使われることが多いのでマクロとして実装されています:

macro circumfix:«< >»($text) is parsed / <-[>]>+ / {
    return $text.comb(rx/\S+/);
}

is parsedトレイトには区切り子の中の文字列を解析する正規表現を与えます。もし指定しない場合は通常のPerl6コードとして解析されます(新しい構文を導入したいときには、これは望む動作ではないでしょう)。 Str.combはパターンを探し、マッチしたテキストのリストを返します。

既存の演算子の「オーバーロード」

(全部ではないにしろ)既存のほとんどの演算子は多重サブルーチンかメソッドであり、新しい型に合わせてカスタマイズできます。 多重サブルーチンを追加することで演算子の「オーバーロード」が実現されます。

class MyStr { ... }
multi sub infix:<~>(MyStr $this, Str $other) { ... }

これは組み込みの「特別な」オブジェクト(StrIntなど)のようにふるまうオブジェクトを書けるということです。

MOTIVATION

ユーザによる新しい演算子の定義や、既存の演算子の「オーバーロード」を許すことは、ユーザ定義型を組み込み型と同じくらい強力で便利なものにします。 もし組み込み型では機能不足になったとしても、コンパイラに変更を加えることなく新しい型に置き換えて状況に対応できます。

これは言語を使うことと言語を改変することのギャップをも取り払います。

SEE ALSO

http://perlcabal.org/syn/S06.html#Operator_overloading

もし技術的な背景(Perl6がこのような演算子その他の変更をどうやって実現しているかなど)に興味があるなら、http://perlgeek.de/en/article/mutable-grammar-for-perl-6を読んで下さい。

コメント

このブログの人気の投稿

Perl の新 class 構文を使ってみる

Perl 5 のオブジェクト指向機能は基本的には Python の影響を受けたものだが、データを名前空間 (package) に bless する機構だけで Perl 4 以来の名前空間とサブルーチンをそのままクラスとメソッドに転換し第一級のオブジェクト指向システムとした言語設計は驚嘆に価する。 実際この言語のオブジェクトシステムは動的型付言語のオブジェクト指向プログラミングに要求されるおよそあらゆる機能を暗にサポートしており、CPAN には Moose を筆頭とした屋下屋オブジェクトシステムが複数存在しているがその多くは Pure Perl ライブラリである。つまり「やろうと思えば全部手書きで実現できる」わけである。 そういうわけで Perl のオブジェクト指向プログラミングサポートは機能面では (静的型検査の不在という現代的には極めて重大な欠如を除けば) 申し分ないのだが、しかし Moose その他の存在が示しているように一つ明らかな欠点がある。記述の冗長さだ。 コンストラクタを含むあらゆるメソッドは第一引数としてレシーバを受ける単なるサブルーチンとして明示的に書く必要があるし、オブジェクトのインスタンス変数 (a.k.a. プロパティ / データメンバ) は bless されたデータに直接的ないし間接的に プログラマ定義の方法 で格納されるためアクセス手段は実装依存である。これはカプセル化の観点からは望ましい性質だが、他者の書いたクラスを継承するときに問題となる。ある日データ表現を変更した親クラスがリリースされると突然自分の書いた子クラスが実行時エラーを起こすようになるわけだ。 そうならないためにはインスタンス変数へのアクセスに (protected な) アクセサを使う必要があるのだが、そのためには親クラスが明示的にそれらを提供している必要があるし、そもそも Perl にはメソッドのアクセス修飾子というものがないので完全な制御を与えるならばオブジェクトの内部状態がすべて public になってしまう。 そのような事情もあり、特にパフォーマンスが問題にならないようなアプリケーションコードでは Moose のようなリッチな語彙を提供するオブジェクトシステムを使うことが 公式のチュートリアルでも推奨 されてきた。Perl コアのオブジェクトシステムの改良は...

去る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 +{} ...

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) ...

Project Euler - Problem 35

問題 原文 How many circular primes are there below one million? 日本語訳 100万未満の巡回素数は何個か? 解答 回転させた数値がすべて素数ということは、すべての桁が奇数でなければいけません(ただし2を除く)。 追記 匿名氏にコメントでご指摘頂いたのでコードを一部修正しました。 いずれかの桁に5がある場合も、回転させると必ず5の倍数が現れるので除外できます。 もっと追記 前の修正に間違いが入っているのをご指摘頂いたので修正しました。 5自体は素数なので、巻き添えで除外してはいけません。 #!/usr/bin/env perl use strict; use warnings; use feature qw/say state/; use List::MoreUtils qw/all none/; sub is_prime($) { state %memos; my $n = shift; return 0 if $n < 2; return 1 if $n == 2; return 1 if $n == 3; return $memos{$n} if exists $memos{$n}; $memos{$n} = none { $n % $_ == 0 } 2 .. sqrt $n; } sub rotate($) { my $n = shift; substr($n, 1) . substr($n, 0, 1); } sub rotations($) { my $n = shift; my %seen = ($n => 1); $seen{$n} = 1 until exists $seen{$n = rotate $n}; keys %seen; } sub is_circular_prime($) { state %memos; my $n = shift; return 0 if $n =~ /[024568]/ and $n != 2 and $n != 5; return $memos{$n} if exists $memos{$n}; my ...

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 (大文字なことに注意) ブロックが自分の宣言されたスコープ内で投げられた例外を捕らえる...