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

Perl 5 to 6 - 例外と制御例外

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

原文は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 26 - 例外と制御例外

SYNOPSIS

try {
    die "OH NOEZ";

    CATCH { 
        say "there was an error: $!";
    }
}

DESCRIPTION

例外はその名前に反してまったく例外的なものではありません。実際のところPerl6では通常の制御フローの一部です。

例外は潜在的なエラー(例えば0除算、存在しないメソッドの呼び出し、型チェック失敗)またはdieその他の関数の明示的な呼び出しによって生成されます。

例外が投げられるとプログラムは呼び出しフレームからCATCHブロックかtryブロックを探し、スタックを完全に巻き戻します(つまりそれまでに呼び出された全部のサブルーチンから無理矢理戻ってくるということです)。 もしCATCHtryも見つからなければプログラムは終了し、運が良ければ役に立つエラーメッセージが表示されます。 どちらか一方が見つかった場合はエラーメッセージは特殊変数$!に格納され、CATCHブロックが実行されます(tryブロックにCATCHブロックがない場合、ブロックはundefを返します)。

ここまでの説明ではまだ例外が例外的なものに思えるかも知れませんが、エラー処理は些末なアプリケーションでもなければ不可欠です。 ただそればかりでなく、通常のreturn文も例外を投げているのです!

これは制御例外と呼ばれ、CONTROLブロックで捕捉することができ、そうしない場合はサブルーチン宣言で暗黙的に捕捉されます。

次の例を考えてみて下さい:

use v6;
my $block = -> { return "block"; say "still here" };

sub s {
    $block.();
    return "sub";
}

say s();

return "block"が制御例外を投げ、現在のブロックから抜ける(したがってstill hereは表示されない)のみならず、サブルーチンからも抜けます。これはsub s...宣言で捕捉されます。 ペイロード——この例では文字列——は戻り値として渡され、最後の行のsayがこれを表示します。

$block.()try { ... }ブロックの中に入れたりCONTROL { ... }ブロックをサブルーチンの本体に置いたりすることで例外を捕捉できます。

他のプログラミング言語とは逆にCATCH/CONTROLブロックはエラーが捕捉されるスコープの(外ではなく)中にあり、字句的変数への完全なアクセスが与えられています。このことは役に立つエラーメッセージを作るのを容易にし、またエラーが処理される前にDESTROYブロックが実行されることを防ぎます。

投げられない例外

Perl6はマルチスレッドの、特に自動並列化の考えを取り入れています。 1スレッドの終了が全スレッドに影響することがないように、「ソフトな」例外が考案されました。

関数がfail($obj)を呼ぶと特別な未定義値を返します。これはペイロードに$obj(大抵の場合はエラーメッセージ)とバックトレース(ファイル名と行番号)を格納しています。 この特殊な未定義値を確認なしに処理しようとすると通常の例外が投げられます。

my @files = </etc/passwd /etc/shadow nonexisting>;
my @handles = hyper map { open($_) }, @files;

この例のhyper演算子はmapに可能な限り動作を並列化するよう伝えます。 nonexistingファイルのオープンに失敗した場合、通常のdie "No such file or directory"では他のファイルオープン処理も中断してしまいます。 しかし失敗したオープン処理は代わりにfail("No such file or directory")を呼ぶので、呼び出し元は@handlesの内容を確認することができ、完全なエラーメッセージにもアクセスできます。

ソフトな例外が気に入らない場合、プログラムの最初にuse fatal;と書くとfail()から生じる例外が即座に投げられるようになります。

MOTIVATION

良いプログラミング言語にはエラー状態を扱うための例外が必要です。 成功したかどうか戻り値をいつも確認することは厄介だし忘れがちです。

伝統的な例外は暗黙の並列化にとっては有害なことがあるので、両者の最上の部分を組み合わせる解法が必要でした: 一度にすべてを殺してしまわず、またどんな情報も取りこぼしてしまわない方法がです。

コメント

このブログの人気の投稿

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

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

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

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

(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 のようなディレクトリ...